多執行緒設計模式:第四篇 - Thread-Per-Message模式和Worker-Thread模式
一,Thread-Per-Message模式
Thread-Per-Message模式是說為每個請求都分配一個執行緒,由這個執行緒來執行處理。這裡包含兩個角色,請求的提交執行緒和請求的執行執行緒。
下面的示例程式碼中,Host 提交一個請求交給另外一個執行緒來處理。
/** * @author koma <[email protected]> * @date 2018-10-18 */ public class Main { public static void main(String[] args) { System.out.println("Main BEGIN"); Host host = new Host(); host.request(10, 'A'); host.request(20, 'B'); host.request(30, 'C'); System.out.println("Main END"); } } public class Host { private final Helper helper = new Helper(); public void request(final int count, final char c) { System.out.println("Request BEGIN"); new Thread() { @Override public void run() { helper.handle(count, c); } }.start(); System.out.println("Request END"); } } public class Helper { public void handle(int count, char c) { System.out.println("Helper BEGIN"); for (int i = 0; i < count; i++) { slowly(); System.out.print(c); } System.out.println(); System.out.println("Helper END"); } private void slowly() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }
Thread-Per-Message模式可以提高程式的響應性,但是並不能保證操作的順序,而且沒有返回值。一般用於簡單伺服器實現,當伺服器主執行緒收到客戶端的請求之後就建立一個新的執行緒去處理客戶端的請求,而伺服器主執行緒則返回繼續接收客戶端請求。
1,Executor 框架
Java 中通常是通過 new Thread 或者 new Runnable 實現類來建立執行緒,在 juc 包中提供了 Executors,ThreadFactory,ExecutorService 另外三種方式來更加方便的建立執行緒或者執行緒池等,在以後的程式碼中,我們推薦使用這種方式建立執行緒。
二,Worker-Thread模式
Worker-Thread模式也被稱為 Background Thread (背景執行緒)模式,這種模式不同於 Thread-Per-Message 模式在任務到來時建立執行緒,而是會先建立一些工作執行緒等待任務到來,從這個角度來說 Worker-Thread 模式也可被稱為執行緒池模式。
下面的示例程式模擬了Worker-Thread模式的工作過程,程式碼如下:
/**
* @author koma <[email protected]>
* @date 2018-10-18
*/
public class Main {
public static void main(String[] args) {
Channel channel = new Channel(5);
channel.startWorkers(); //啟動工作執行緒
new ClientThread("ClientA", channel).start();
new ClientThread("ClientB", channel).start();
new ClientThread("ClientC", channel).start();
}
}
public class Channel {
private static final int MAX_REQUESTS = 100; //定義請求佇列最大長度
private final Queue<Request> queue;
private final WorkerThread[] threadPool;
public Channel(int threads) {
queue = new LinkedList<>();
threadPool = new WorkerThread[threads];
for (int i = 0; i < threads; i++) {
threadPool[i] = new WorkerThread("Worker-"+i, this);
}
}
public void startWorkers() {
for (int i = 0; i < threadPool.length; i++) {
threadPool[i].start();
}
}
public synchronized void putRequest(Request request) throws InterruptedException {
while (queue.size() >= MAX_REQUESTS) {
wait();
}
queue.offer(request);
notifyAll();
}
public synchronized Request takeRequest() throws InterruptedException {
while (queue.size() <= 0) {
wait();
}
Request request = queue.poll();
notifyAll();
return request;
}
}
public class Request {
private final String name;
private final int number;
private static final Random random = new Random();
public Request(String name, int number) {
this.name = name;
this.number = number;
}
public void execute() {
System.out.println(Thread.currentThread().getName()+" execute "+this);
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "[ Request from "+name+", No."+number+" ]";
}
}
public class ClientThread extends Thread {
private final Channel channel;
private static final Random random = new Random();
public ClientThread(String name, Channel channel) {
super(name);
this.channel = channel;
}
@Override
public void run() {
try {
for (int i = 0; true; i++) {
Request request = new Request(getName(), i);
channel.putRequest(request);
Thread.sleep(random.nextInt(1000));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class WorkerThread extends Thread {
private final Channel channel;
public WorkerThread(String name, Channel channel) {
super(name);
this.channel = channel;
}
@Override
public void run() {
while (true) {
try {
Request request = channel.takeRequest();
request.execute();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
示例中 Channel 類和生產者-消費者模式中一樣,我們也可以使用 Java 中的 BlockingQueue 來實現,從而省去我們自己寫 wait/notify 邏輯的複雜性。
Worker-Thread模式中應用到了生產者-消費者模式,因為提交請求的執行緒和處理請求的執行緒不是同一個執行緒。Worker-Thread模式的優點在於它的工作執行緒是預先啟動好的,這樣節省了執行緒啟動的時間損耗,提高了程式的效能,而且工作執行緒是可以重複利用的,節省了程式的資源消耗。
1,ThreadPoolExecutor
juc包通過 ThreadPoolExecutor 類輕鬆的實現了Worker-Thread模式,通過該類可以對工作執行緒做非常精細的控制,如下:
- 指定執行緒池的大小
- 指定建立執行緒的方式:提前建立,按需建立
- 指定執行緒建立的工廠類:更加細緻的控制創建出來的執行緒
- 指定多長時間後終止不再需要的執行緒
- 指定傳遞任務的佇列的排隊方式
- 指定當佇列飽和時拒絕請求的方式
- 指定執行緒執行工作前,執行工作後的處理動作
通常我們會通過 Executors 類中的靜態方法來建立執行緒池。利用 Executor 框架改寫後的 WorkerThread 類,以及啟動 Worker 的程式碼如下:
public class WorkerThread implements Runnable {
//程式碼不變,僅僅把 WorkerThread 改成實現 Runnable 介面
//為了傳遞到 Executors 類中
}
public class Channel {
//建立工作執行緒池
private final ExecutorService executorService = Executors.newFixedThreadPool(5);
//其它程式碼不變,記得將原來的 threadPool 程式碼註釋掉
//從執行緒池中啟動工作執行緒
public void startWorkers() {
executorService.execute(new WorkerThread(this));
}
}