多線程學習-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教育課後總結。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章