java執行緒之間的通訊(等待/通知機制)
阿新 • • 發佈:2019-01-10
執行緒開始執行,擁有自己的棧空間,就如同一個指令碼一樣,按照程式碼一步步的執行直到終止。但是,每個執行中的執行緒,如果僅僅是孤立地執行,那麼沒有太大的價值,但如果多個執行緒能夠相互配合完成工作,這將會帶來巨大的價值。
而java多執行緒的等待和通知機制就是用來完成執行緒之間的通訊。
一個執行緒修改了一個物件的值,而另一個執行緒感知到了變化,然後進行相應的操作,整個過程開始於一個執行緒,而最終執行又是另一個執行緒。前者是生產者,後者是消費者,這種模式隔離了”怎麼做”,”做什麼”,在功能層面上實現瞭解耦,體系結構上具備了良好的伸縮性。實現這種機制最簡單的方法就是讓消費者執行緒不斷的迴圈檢查變數是否符合預期
while(value!=desire){
xxx.wait();
}
doSomething();
在while迴圈中設定不滿足條件的條件,如果條件滿足則退出while迴圈,從而完成消費者的工作。讓消費者執行緒wait的作用是讓其進入WAITING狀態,當生產者執行緒更新的value值的時候就進行notify喚醒該執行緒,讓消費者執行緒完成doSomething.
示例程式碼:
public class WaitNotify{
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) throws Exception{
Thread waitThread = new Thread(new Wait(),"WaitThread");
waitThread.start();
TimeUtil.SECONDS.sleep(1);
Thread notifyThread = new Thread(new Notify(),"NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable{
public void run(){
//加鎖
synchronized(lock){
//當條件不滿足的時候,進入WAITTING狀態,同時釋放lock鎖
while(flag){
System.out.println("flag is true ");
lock.wait();
}
//條件滿足
System.out.println("doSomething");
}
}
}
static class Notify implements Runnable{
public void run(){
//加鎖
synchronized(lock){
//獲取lock的鎖,然後進行通知,通知不會釋放lock鎖
//直到發出通知的執行緒執行完畢釋放了lock鎖,WaitThread執行緒才能從wait方法返回
lock.notifyAll();
System.out.println("flag is false now");
flag = false;
}
}
}
輸出內容如下:
flag is true
flag is false now
doSomething
現在我們來看呼叫 wait(),notify(),notifyAll()的注意事項:
- 呼叫 wait(),notify(),notifyAll()時需要先對呼叫物件加鎖。
- 呼叫wait()方法後,執行緒由RUNNING變為WAITING,並將當前執行緒放置於物件等待佇列
- notify(),notifyAll()方法被呼叫後,等待執行緒依然不會從wait()方法返回,而是等呼叫notify(),notifyAll()的執行緒釋放該鎖之後,等待執行緒才有機會從wait()返回。
- notify()方法將等待佇列中的一個等待執行緒從等待佇列中移到同步佇列中,而 notifyAll()方法則是把所有等待執行緒從等待佇列中移到同步佇列中,被移動的執行緒的狀態由WAITING變成BLOCKED
- 從wait()方法返回的前提是獲得了呼叫物件的鎖。
現在我們可以發現,等待/通知機制依託於同步機制,其目的就是確保等待執行緒從wait()方法返回時能夠感知到通知執行緒對變數的修改。
等待/通知的經典範式
我們在如上的示例程式碼中提煉出等待/通知的經典範式,該正規化分為兩部分,分別為等待方(消費者)和通知方(生產者)
等待方的原則:
- 1) 獲取物件的鎖
- 2)如果條件不滿足,那麼呼叫鎖的wait()方法,使該執行緒進入waiting,被通知後依然要檢查條件
- 條件滿足則執行對應的邏輯
虛擬碼:
synchronized(物件){
while(條件不滿足){
物件.wait();
}
對應的邏輯處理
}
通知方的原則:
- 1) 獲取物件的鎖
- 2)改變條件
- 3)通知所有等待在該物件上的執行緒
虛擬碼:
synchronized(物件){
改變條件
物件.notifyAll();
}