java多執行緒設計模式之Guarded Suspension
想象一個場景,你在排隊領軍訓的裝備,當你排隊到視窗的時候,工作人員對你說,等一下,讓我叫後勤先去倉庫取下裝備再給你,於是你等到工作人員取回裝備才能領走裝備。
抽象為一個java程式模型:你是一個執行緒ClientThread,負責取資料Request,暫時存放裝備的視窗是一個佇列RequestQueue,後勤人員是一個存放裝備的執行緒ServerThread,於是可以用一下程式碼表示:
首先是存放資料到佇列的執行緒類ClientThread:
public static class ClientThread extends Thread{ private RequestQuue requestQuue; public ClientThread(RequestQuue requestQuue,String name) { super(name); this.requestQuue = requestQuue; } public void run() { // TODO Auto-generated method stub for (int i = 0; i < 1000; i++) { Request request = new Request("request"+i); requestQuue.putRequest(request); System.out.println(Thread.currentThread().getName()+" put "+request.getName()); try { sleep(new Random().nextInt(200)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
然後是取資料的執行緒類ServerThread:
public static class ServerThread extends Thread{ private RequestQuue requestQuue; public ServerThread(RequestQuue requestQuue,String name) { super(name); this.requestQuue = requestQuue; } public void run() { // TODO Auto-generated method stub for (int i = 0; i < 1000; i++) { Request request = requestQuue.getRequest(); System.out.println(Thread.currentThread().getName()+" get "+request.getName()); try { sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
這裡故意讓取得速度比存放快,才容易出現等待狀態。
這是任務類Request:
public class Request { private String name; public Request(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { // TODO Auto-generated method stub return name; } }
核心類RequestQueue:
public class RequestQuue {
private final LinkedList queue = new LinkedList();
public synchronized Request getRequest(){
while (queue.size() <= 0) {
try {
wait();
} catch (InterruptedException e) {
// TODO: handle exception
}
}
return (Request) queue.removeFirst();
}
public synchronized void putRequest(Request requst) {
// TODO Auto-generated method stub
queue.addLast(requst);
notifyAll();
}
}
看到中間這一段:
while (queue.size() <= 0) {
try {
wait();
}
這是今天的重點,就像你要取裝備的時候工作人員告訴你暫時沒裝備,等後勤把裝備取來一樣,你總不能砸窗去裡面搶吧?這是對於RequestQueue的保護,防止出現沒資料卻仍然執行getRequest導致出現java.util.NoSuchElementException異常。
wait方法是Object的方法,作用是使已經獲取一個物件鎖的執行緒進入等待狀態,並釋放自己的物件鎖,此時如果有其他執行緒在等待,則會競爭這把鎖以執行程式碼。
看下putRequest中,當存入一個Request的時候,執行了notifyAll方法,這也是Object的方法,作用是喚醒正在當前執行緒持有的物件鎖上wait的所有執行緒(notify則是隨機喚醒一個執行緒),這樣相當於後勤將裝備取過來,大叫一聲:“可以拿裝備了!”。於是之後wait的getRequest執行緒被喚醒,開始取資料。
注意,wait外圍要加一層迴圈判斷,這是因為執行緒在wait有可能在不執行notyfy/notifyAll方法的情況下醒來,為了保險要加一個迴圈判斷。
這就是一個最簡單的生產者消費者的模型,一個執行緒生產資料,另一個執行緒消費資料,消費資料的執行緒如果遇到沒資料的情況下,則等待資料的出現再執行。