1. 程式人生 > >多執行緒學習-day-04等待(wait)和通知(notify/notifyAll)

多執行緒學習-day-04等待(wait)和通知(notify/notifyAll)

執行緒基礎、執行緒之間的共享和協作

(目前會將一些概念簡單描述,一些重點的點會詳細描述)
學習目標:等待和通知

一、應用場景:

一個執行緒修改了一個值,另一個執行緒感受到了值的變化,進行相應的操作。前一個執行緒類比於一個生產者,後一個執行緒是消費者。如何讓消費者感受到生產者的一個值的變化呢?

解決方案一:

輪詢:每隔一分鐘就去輪詢一次,總有一個時間點能夠獲取到生產者的變換。比如煲湯,每個一分鐘就去看一下是否煲好了。結果:這樣會很累,很佔用資源。

輪詢的缺點:很難確保一個及時性,每隔一段時間就要去操作一次,資源開銷很大,做很多無用功。

解決方案二:

等待和通知機制方式:當一個執行緒呼叫了wait()方法,會進入一個等待狀態,而另外一個執行緒對值進行操作後,呼叫notify()或者notifyAll()方法後,通知第一個執行緒去操作某件事情。注意:wait()、notify()/notifyAll()是物件上的方法。

wait()等待方會怎麼做?

1、獲取物件的鎖;一定是在迴圈裡面去操作;

2、迴圈裡判斷是否滿足條件,不滿足條件呼叫wait()方法,一直等待;

3、滿足條件,執行業務邏輯;

notify()、notifyAll()會怎麼做?

1、依然要獲取物件的鎖;

2、改變相關條件;

3、通知所有等待在物件的執行緒

以上介紹了wait、notify/notifyAll的標準正規化。

三、notify()和notifyAll()區別:

應該儘量應用notifyAll(),使用notify()的話,jvm會執行已經加入等待執行緒棧裡面的第一個執行緒,給我們一種感觀就是隨機的選擇了一種執行緒,如果該執行緒達到條件就正好執行那一條,其實這是一個誤區,而是jvm會選擇線上程棧裡面的第一個執行緒。因此如果用notify()的話,可能會造成訊號丟失的情況。

舉例應用:比如一個快遞,發貨地址是長沙,收貨地址是深圳,初始發貨公里(km)為0。則改變公里數(km)以及改變收貨地址,來操作等待/通知的情景

先定義一個Express類

public class Express {
	// 定義一個發貨地,這裡定為長沙
	public static final String CITY = "ChangSha";
	// 定義一個千米數,表示貨已經走了多少千米了
	private int km;
	// 定義一個發貨城市,表示貨從哪個城市出發
	private String site;

	// 定義無參構造方法
	public Express() {
	}

	// 定義有參構造方法
	public Express(int km, String site) {
		this.km = km;
		this.site = site;
	}

	// 定義改變km的方法
	public synchronized void changeKm() {
		// 定義已經走了101千米了
		km = 101;
		// 千米數改變之後,就傳送通知
		notifyAll();
	}

	// 定義改變城市的方法
	public synchronized void changeSite() {
		// 定義深圳收貨
		site = "ShenZhen";
		// 城市改變後,就傳送通知
		notifyAll();
	}

	// 定義千米等待的方法,等待km改變之後,就取消等待,輸出結果
	public synchronized void waitKm() {
		// 當km小於100時,還沒有超過100km,則繼續等待超過100km後的通知
		while (this.km <= 100) {
			try {
				wait();
				System.out.println("當前執行緒:" + Thread.currentThread().getId() + " 還沒有運送超過100km,還在等通知!");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		// 收到通知後,等待結束,輸出結果
		System.out.println("當前km = " + this.km + " 已經運送超過100km了,進行資料庫儲存操作!");
	}

	// 定義城市等待的方法,等待城市改變之後,就取消等待,輸出結果
	public synchronized void waitSite() {
		// 當site還沒有到長沙,則繼續等待到長沙之後的通知
		while (CITY.equals(this.site)) {
			try {
				wait();
				System.out.println("當前執行緒:" + Thread.currentThread().getId() + " 還沒有到達長沙,還在等通知!");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		// 收到通知後,表示到達長沙,等待結束,輸出結果
		System.out.println("當前site = " + this.site + "已經到達深圳,要告訴使用者下來拿快遞!");
	}
}

定義測試類TestWN(Test Wait Notifyall縮寫)

public class TestWN {

	// 例項化Express物件,並呼叫物件鎖
	private static Express express = new Express(0, Express.CITY);

	// 定義方法,檢查km變化情況,沒有達到條件一直等待
	public static class ThreadKm extends Thread {
		@Override
		public void run() {
			// 因為Express裡面的方法都是物件鎖,所以直接呼叫
			express.waitKm();
		}
	}

	// 定義方法,檢查site變化情況,沒有達到條件一直等待
	public static class ThreadSite extends Thread {
		@Override
		public void run() {
			// 因為Express裡面的方法都是物件鎖,所以直接呼叫
			express.waitSite();
		}
	}

	public static void main(String[] args) throws InterruptedException {

		// 設定site變化
		for (int i = 0; i < 3; i++) {
			new ThreadSite().start();
		}

		// 設定km變化
		for (int i = 0; i < 3; i++) {
			new ThreadKm().start();
		}

		// 休眠3秒,然後改變里程數
		Thread.sleep(1000);
		express.changeKm();
		express.changeSite();
	}
}

控制檯輸出結果:
當前執行緒:15 還沒有運送超過100km,還在等通知!
當前km = 101 已經運送超過100km了,進行資料庫儲存操作!
當前執行緒:14 還沒有運送超過100km,還在等通知!
當前km = 101 已經運送超過100km了,進行資料庫儲存操作!
當前執行緒:13 還沒有運送超過100km,還在等通知!
當前km = 101 已經運送超過100km了,進行資料庫儲存操作!
當前執行緒:12 還沒有到達長沙,還在等通知!
當前site = ShenZhen已經到達深圳,要告訴使用者下來拿快遞!
當前執行緒:11 還沒有到達長沙,還在等通知!
當前site = ShenZhen已經到達深圳,要告訴使用者下來拿快遞!
當前執行緒:10 還沒有到達長沙,還在等通知!
當前site = ShenZhen已經到達深圳,要告訴使用者下來拿快遞!

以上介紹的就是等待/通知標準正規化的程式碼演示。

 

join()方法

執行緒A,執行了執行緒B的join方法,執行緒A必須要等待B執行完成了以後,執行緒A才能繼續自己的工作

呼叫yield() sleep()wait()notify()等方法對鎖有何影響? 

執行緒在執行yield()以後,持有的鎖是不釋放的

sleep()方法被呼叫以後,持有的鎖是不釋放的

調動方法之前,必須要持有鎖。呼叫了wait()方法以後,鎖就會被釋放,當wait方法返回的時候,執行緒會重新持有鎖

調動方法之前,必須要持有鎖,呼叫notify()方法本身不會釋放鎖的

來自享學IT教育課後總結。