Java併發基礎(4)——“等待通知”和“等待喚醒”
目錄
一、執行緒之間的協作
在多執行緒中,經常會出現這種情況:一個執行緒改變了某個變數的值,而另一個執行緒感受到這個變數的值發生了變化,從而繼續做某些事情
通常我們稱前面一個執行緒為生產者(通知方),後面一個執行緒為消費者(等待方)
那麼問題來了,消費者如何知道生產者是否改變了變數值呢?
一種簡單粗暴的方式就是輪詢,消費者不斷去檢查該變數的值。缺點也顯而易見:缺乏及時性,浪費資源
這時,我們可以採用“等待通知”機制來實現這個要求。在Java中,我們常用synchronized 配合 wait()、notify()、notifyAll() (推薦)這三個方法就能實現該機制。
二、“等待通知”機制
“等待通知”機制的標準正規化
等待方 | 通知方 |
1、拿到物件的鎖 | 1、拿到物件的鎖 |
2、迴圈裡判斷條件是否滿足 不滿足則等待 | 2、改變條件 |
3、條件滿足執行業務 | 3、通知所有等待的執行緒 |
2.1 demo
public class People { private Integer height;//身高 public People() { } public People(Integer height) { super(); this.height = height; } //改變身高 public synchronized void changeHeight() { this.height = 180; notifyAll(); } //等待身高變化 public synchronized void waitHeight() { while (this.height < 180) { try { System.out.println("waitHeight ["+Thread.currentThread().getId() +"] 進入等待狀態."); wait(); System.out.println("waitHeight ["+Thread.currentThread().getId() +"] 被喚醒."); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("waitHeight ["+Thread.currentThread().getId() +"] 執行完畢,此時身高:" + this.height); } }
測試程式碼
public class TestWaitAndNotify { private static People people = new People(100); //等待方 private static class CheckHeight extends Thread{ @Override public void run() { people.waitHeight(); } } public static void main(String[] args) throws InterruptedException { for(int i = 0;i < 3;i++){ new CheckHeight().start(); } Thread.sleep(1000); //通知方 people.changeHeight(); } }
我們在main方法中新起了三個執行緒
這三個執行緒通過競爭,先拿到鎖的執行緒執行waitHeight()
當執行到wait()時會進入等待狀態並釋放掉鎖,另外兩個執行緒競爭,同理,直到三個執行緒都在等待
等主執行緒執行people.changeHeight();時,會喚醒所有在等待的執行緒,繼續執行自己的業務
執行結果如下
注意這裡使用了notifyAll() 喚醒所有等待的執行緒
如果使用notify(),隨機喚醒一個,如果有其他條件的執行緒在等待,可能也會被喚醒,喚醒後檢查條件不符合後又繼續等待
三、“等待喚醒”機制
在上面示例中,進入等待狀態的執行緒會一直等待,直到喚醒。這顯然不符合實際場景。
接下來學習“等待超時”機制
“等待超時”機制的標準正規化:
當前時間now
等待時間T
當超過now+T時間後超時overtime
剩餘時間為retime
while(判斷條件不滿足 &&retime> 0){
wait(retime);//等待
retime=overtime - now;//更新剩餘時間
}
3.1 demo
public class People {
private Integer height;//身高
public People() {
}
public People(Integer height) {
super();
this.height = height;
}
//改變身高
public synchronized void changeHeight() {
this.height = 180;
notifyAll();
}
//等待身高變化
public synchronized void waitHeight(long mills) throws InterruptedException {
if(mills < 0){
//mills < 0 永不超時
System.out.println("waitHeight ["+Thread.currentThread().getId() +"] 進入等待狀態.");
wait();
System.out.println("waitHeight ["+Thread.currentThread().getId() +"] 被喚醒.");
}else {
//超時時間
long overtime = System.currentTimeMillis() + mills;
//剩餘時間
long retime = mills;
//判斷連線池中是否有連線且剩餘時間是否大於0
while(this.height < 180 && retime > 0){
System.out.println("waitHeight ["+Thread.currentThread().getId() +"] 進入等待狀態.");
wait(retime);
System.out.println("waitHeight ["+Thread.currentThread().getId() +"] 被喚醒.");
//如果被喚醒,則要更新剩餘時間
retime = overtime - System.currentTimeMillis();
}
}
System.out.println("waitHeight ["+Thread.currentThread().getId() +"] 執行完畢,此時身高:" + this.height);
}
}
public class TestWaitAndNotify {
private static People people = new People(100);
//等待方
private static class CheckHeight extends Thread{
@Override
public void run() {
try {
//修改等待時間,可以看到不同結果
people.waitHeight(-1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
new CheckHeight().start();
Thread.sleep(2000);
//通知方
people.changeHeight();
}
}
通過修改等待時間,可以看到不同的結果
如果在等待時間內,通知方執行people.changeHeight(); 修改身高,則滿足等待方的條件,最終身高則是180
如果等待時間短,則等待方超時後,就結束迴圈,什麼也不做,最終身高還是100
---------------------------------------------------------------------------------------------------------------------------------------------------
如果我的文章對您有點幫助,麻煩點個贊,您的鼓勵將是我繼續寫作的動力
如果哪裡有不正確的地方,歡迎指正
如果哪裡表述不清,歡迎留言討論