1. 程式人生 > 其它 >Java併發基礎(4)——“等待通知”和“等待喚醒”

Java併發基礎(4)——“等待通知”和“等待喚醒”

技術標籤:java併發java多執行緒併發程式設計

目錄

一、執行緒之間的協作

二、“等待通知”機制

2.1 demo

三、“等待喚醒”機制

3.1 demo


一、執行緒之間的協作

在多執行緒中,經常會出現這種情況:一個執行緒改變了某個變數的值,而另一個執行緒感受到這個變數的值發生了變化,從而繼續做某些事情

通常我們稱前面一個執行緒為生產者(通知方),後面一個執行緒為消費者(等待方)

那麼問題來了,消費者如何知道生產者是否改變了變數值呢?

一種簡單粗暴的方式就是輪詢,消費者不斷去檢查該變數的值。缺點也顯而易見:缺乏及時性,浪費資源

這時,我們可以採用“等待通知”機制來實現這個要求。在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

---------------------------------------------------------------------------------------------------------------------------------------------------

如果我的文章對您有點幫助,麻煩點個贊,您的鼓勵將是我繼續寫作的動力

如果哪裡有不正確的地方,歡迎指正

如果哪裡表述不清,歡迎留言討論