在 Worker Thread Pattern 中,worker thread 會依序抓一件工作來執行。當沒有工作時,worker thread 會停下來等待新的工作過來。
worker thread 也有人稱為 Background Thread。也有人把視點放在管理 worker thread 的地方,稱之為 thread pool。
程式範例:
所有參與者
Client
Client 會建立 Requset,傳給 Channel。例如 ClientThread 類別。
Channel
Channel 從 Client 取得 Request,傳給 Worker。例如 Channel 類別。
Worker
Worker 會從 Channel 取得 Request,並執行這份工作。當工作結束以後,會去拿取下一個 Request。例如 WorkerThread 類別。
Request
Request 用來表示工作,存放這份工作所需要的資料。例如 Request 類別。
重點
啟動執行緒也需要花時間
Worker Thread 重複使用執行緒,不像 Thread-Per-Message Pattern,它不會一直去建立新的執行緒。
控制承載量
提高 Worker 的數量,可以提高並行處理的工作量。但如果準備的 Worker 數量比同時間的工作還要多,有些 worker 也派不上用場。所以有必要配合實際的需要,來調整 worker 的數量。
worker 的數量,可以有機動性變化:
- 最先從某個一定量的 worker 開始
- 當工作量增加時,增加 worker 數量
- 不過增加太多會用光記憶體,所以到達某個上限要停止增加
- 相反地,工作減少時(也就是待命中的 worker 增加時),就結束掉一些 worker。
Request 的數量
增加 Channel 可以存放的 Request 數量,可緩衝 Client 與 Worker 的處理速度差,但是如果儲存太多 Request,也會佔用大量記憶體資源。
Invocation 與 Execution 的分離
在 Worker Thread Pattern 與 Thread-Per-Message Pattern 中,將方法的啟動與執行分開,這也是 Command Pattern 的應用。有以下好處:
- 提高回應性
- 控制優先順序(可控制 Request 的處理優先順序)
- 可取消,可重複執行(雖然呼叫了,但是沒有執行。只要產生 Request,就可以重複執行)
- 分散處理的第一步(呼叫跟執行可以在不同的電腦上執行,相當於 Request 可以透過網路傳送到另外一台電腦上)
增加工作的種類
可以藉由建立 Request 的子類別,來增加工作的種類,Channel 以及 Worker 都不需要修改,即使增加工作的種類,Worker 都只是呼叫 Request 的 execute 方法而已。
如果只有一個 worker
那就不需要共用互斥了。
問題
1. 改寫 Channle 類別,使得請求傳進來時,就產生一個新的執行緒來處理請求。
改寫後的 Channel 類別。
public final class Channel {
public Channel(int threads) {
}
public void startWorkers() {
}
public void putRequest(final Request request) {
new Thread() {
public void run() {
request.execute();
}
}.start();
}
}
2. 以下的 GUI 程式中,預期結果是每秒之後的 label 會遞增顯示新的數字。但是結果是十秒之後直接顯示 9。請修改程式達成預期結果。
原因就在於說 event-dispatching thread 要離開 actionPerformed 方法之後才會更新畫面。
採取 Thread-Per-Message 解決這個問題,在 countUp 方法中,啟動 invokerThread 執行緒。這個執行緒執行實際的累算動作。呼叫出 CountUp 的執行緒(Event Dispatching Thread) 啟動 invokerThread,馬上就從 countUp 方法回來。
invokerThread 當中,會從 0 開始進行累算,再以 sleep 方法休息約一秒。invokerThread 並非 event-dispatching thread。為了讓 event-dispatching thread 執行 setText 方法,就要使用 SwingUtilities.invokeLater(executor)
,其中 executor 是一個 Runnable 物件,它執行了 setText 方法。
3. 為了使程式在 5 秒內結束,將 Main 類別做了修改,請做出下列的其他修改。
-
在 Channel 類別加上 stopAllWorker 方法。這個方法可以用來結束掉所有 Channel 類別所管理的 WorkerThread 的執行緒。
-
在 ClientThread 類別加上 stopThread 方法,這個方法可以用來結束掉 ClientThread 的執行緒。
修改後的 Main 類別。
public class Main {
public static void main(String[] args) {
Channel channel = new Channel(5); // 工人執行緒個數
channel.startWorkers();
ClientThread alice = new ClientThread("Alice", channel);
ClientThread bobby = new ClientThread("Bobby", channel);
ClientThread chris = new ClientThread("Chris", channel);
alice.start();
bobby.start();
chris.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
alice.stopThread();
bobby.stopThread();
chris.stopThread();
channel.stopAllWorkers();
}
}
我們不去用 Thread 類別的 stop 方法,這是因為 stop 方法即使是對鎖定的執行緒,也能讓它結束,所以較不安全。
採取 Two Phase Termination Pattern 來解決此問題。