1. 程式人生 > >java多執行緒設計模式之Guarded Suspension

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方法的情況下醒來,為了保險要加一個迴圈判斷。
這就是一個最簡單的生產者消費者的模型,一個執行緒生產資料,另一個執行緒消費資料,消費資料的執行緒如果遇到沒資料的情況下,則等待資料的出現再執行。