在前面兩篇文章當中,咱們介紹了synchronized
的基本使用和原理,可是在使用synchronized
保證數據一致性的同時,咱們但願可以讓線程之間進行一些交互邏輯,也是咱們今天要介紹的等待/通知模型,那麼就須要使用到wait/notify
。bash
下面,咱們先介紹等待/通知機制的相關方法,首先要說明兩點:ui
Object
定義的方法Object
對象所關聯的鎖,也就是說它們須要位於synchronized
修飾的同步代碼塊中。**(a) wait() ** 調用該方法的線程進入等待狀態,並釋放它所獲取的對象鎖,只有出現這兩種狀況之一,它纔會從wait
方法中返回,不然將會一直處於等待狀態:spa
notify / notifyAll
方法通知該線程,而且該線程獲取到了對象鎖(b) wait(long) / wait(long, int) 和wait
方法相同,差異是增長一種從wait
方法返回的狀況:等待的時間已經到了,而且獲取到了對象鎖。線程
(c) notify() 通知位於等待隊列中的第一個線程,使其從wait()
方法返回,而被通知的線程的繼續執行須要等到它得到對象所爲止。 須要注意,調用notify
方法後,並不會馬上釋放它所持有的對象鎖,這須要等到它執行完同步代碼塊爲止。code
(d) notifyAll() 與notify()
相似,可是它是通知全部在對象上等待的線程。對象
經過上面的介紹,咱們能夠看到,在整個等待/通知機制當中,線程被掛起時主要有如下三種狀態:等待狀態、超時等待狀態、阻塞狀態,這些狀態都是經過synchroized
所修飾的對象來實現的。隊列
在前面咱們介紹synchronized
原理的時候,曾經說過每一個對象都會和一個Monitor
相關聯,其實每一個Monitor
又包含有兩個隊列:等待隊列和同步隊列,其中等待隊列中存放是進入等待狀態的線程,而同步隊列中存放的是等待獲取鎖的線程。ip
下面,咱們經過一段簡單的僞代碼來當即兩個線程的狀態轉換過程:同步
synchronized public void waitThread() {
//執行a方法.
wait();
//執行b方法
}
synchronized public void notifyThread() {
//執行c方法
notify();
//執行d方法
}
複製代碼
咱們有AB
兩個線程,咱們模擬如下的一系列行爲:it
(1) A 線程執行 waitThread 方法 此時因爲對象鎖沒有被任何線程持有,所以,A
線程成爲對象鎖的持有者:
B
線程執行
notifyThread
方法時,因爲此時對象鎖已經被
A
線程持有,所以它被加入到同步隊列中:
(3) A 線程執行 a 方法
**(4) A 線程執行 wait 方法 ** 當
A
線程執行
wait
方法後,它會釋放對象鎖,並加入到同步隊列當中,而
B
線程則成爲對象鎖新的持有者:
(5) B 線程執行 c 方法
(6) B 線程執行 notify 方法 此時會喚醒等待隊列中
A
線程,可是此時
B
線程仍然持有對象鎖,所以,
A
線程只能被加入到同步隊列:
(7) B 線程執行 d 方法
(8) B 線程從 notifyThread 方法返回 此時
A
線程從新獲取到對象鎖,所以它被從同步隊列中取出,繼續執行接下來的邏輯:
(9) A 線程執行 b 方法
(10) A 線程從 waitThread 方法中返回 當
A
線程從同步方法返回以後,那麼會釋放它所持有的鎖
對於等待/通知模型,咱們能夠總結出它的經典範式,分別針對等待方和通知方。
等待方遵循以下的原則:
wait
方法,被通知後仍然須要檢查條件對應的僞代碼爲:
synchronized( 對象 ) {
while( 條件不知足 ) {
對象.wait();
}
對應的處理邏輯
}
複製代碼
通知方遵循以下的原則:
synchronized( 對象 ) {
改變條件;
對象.notifyAll();
}
複製代碼