Java併發讀書筆記:線程通訊之等待通知機制

在併發編程中,保證線程同步,從而實現線程之間正確通訊,是一個值得考慮的問題。本篇將參考許多著名書籍,學習如何讓多個線程之間相互配合,完成咱們指定的任務。面試

固然本文只是學習了一部分線程間通訊的方法,還有一些例如使用Lock和Condition對象,管道輸入輸出、生產者消費者等內容,咱們以後再作學習。編程

synchronized 與 volatile

synchronized關鍵字是Java提供的互斥的內置鎖,該鎖機制不用顯式加鎖或者釋放鎖。互斥執行的特性能夠確保對整個臨界區代碼的執行具備原子性,同步機制保證了共享數據在同一個時刻只被一個線程使用併發

回顧如下synchronized的底層實現:學習

咱們能夠對下面這段代碼進行反編譯:javap -v TestData.classthis

public class TestData {
    public static synchronized void m1(){}
    public synchronized void m2(){}
    public static void main(String[] args) {
        synchronized (TestData.class){
        }
    }
}

編譯結果以下:
線程

雖然同步方法和代碼塊的實現細節不一樣,可是歸根結底:JVM對於方法或者代碼塊的實現是基於對Monitor對象的進入和退出操做code

以同步代碼塊舉例:對象

  • monitorenter指令被安排到了代碼塊開始位置,monitorexit被安排到代碼塊正常結束和異常處
  • 任何對象都有一個monitor與之相關聯,當一個monitor被持有以後,它將會出於鎖定狀態。
  • 當JVM執行到monitorenter指令時,將會嘗試去獲取當前對象對應的monitor的全部權。
    • 若其餘前程已經有monitor的全部權,那麼當前線程將會進入同步隊列(SynchronizedQueue),陷入阻塞狀態(BLOCKED),直到monitor被釋放。
    • 若monitor進入數爲0,線程能夠進入monitor,此時該線程稱爲monitor的持有者(owner),並計數加一。
    • 若當前線程已經擁有monitor,是容許從新進入該monitor的,此時計數加一。
  • 得到鎖,鎖計數加一。失去鎖,計數減一。計數爲0,即爲釋放鎖。釋放鎖的操做將會喚醒阻塞在同步隊列中的的線程,使其從新得到嘗試對monitor的獲取。

下圖源自《Java併發編程得藝術》4-2
blog


新Java內存模型中提供了比鎖更加輕量級的通訊機制,它加強了volatile的內存語義,讓volatile擁有和鎖同樣的語義:告知程序任何對volatile修飾變量的訪問都要從共享內存中獲取,對它的改變必須同步刷新回共享內存,保證了線程對變量訪問的可見性

關於volatile的重點學習,以後再作總結。

等待/通知機制

等待/通知相關的方法被定義在java.lang.Object上,這些方法必須由鎖對象來調用。同步實例方法爲this,靜態方法爲類對象,代碼塊的鎖是括號裏的玩意兒。

這些方法必須須要獲取鎖對象以後才能調用,也就是必需要在同步塊中或同步方法中調用,不然會拋出IllegalMonitorStateException的異常。

等待

wait() : 調用該方法的線程進入WAITING狀態,並釋放對象的鎖,此時當前線程只有被其餘線程通知或中斷纔會返回。

wait(long)wait(long, int):進入TIMED_WAITING狀態,釋放鎖,當前線程有通知或中斷會返回,時間到了也會返回。

通知

notify() : 當前線程通知一個在該對象上等待的另外一線程,被喚醒的線程從等待隊列(WAITING)被移動到同步隊列(BLOCKED)中,意思是被喚醒的線程不會當即執行,須要等當前線程釋放鎖以後,而且在同步隊列中的線程獲得了鎖才能執行。
notifyAll() :當前線程通知全部等待在該對象上的線程,將全部在等待隊列中的線程所有移到同步隊列中。

假設A和B須要獲取同一把鎖,A進入以後,B進入同步隊列,陷入阻塞(BLOCKED)。

若是A中調用鎖的wait()方法,A釋放鎖,並陷入等待(WAITING)。此時另一個線程B獲取的當前鎖,B運行。

若是此時B中調用鎖的notify()方法,A被喚醒,從等待隊列轉移到同步隊列,只有B運行完畢了,鎖被釋放了,A拿到鎖了,A纔出來運行。

等待/通知機制依託於同步機制,確保等待線程從wait()方法返回時可以感知到通知線程對變量作出的修改

面試常問的幾個問題

sleep方法和wait方法的區別

sleep()和wait()方法均可以讓線程放棄CPU一段時間,進入等待(WAITING)狀態

sleep()靜態方法定義在Thread類中,wait()定義在Object類中。

若是線程持有某個對象的監視器,wait()調用以後,當前線程會釋放鎖,而sleep()則不會釋放這個鎖

關於放棄對象監視器

對於放棄對象監視器,wait()方法和notify()/notifyAll()有必定區別:

鎖對象調用wait()方法以後,會當即釋放對象監視器。而notify()/notifyAll()則不會當即釋放,而是等到線程剩餘代碼執行完畢以後纔會釋放監視器。


參考書籍:《Java併發編程的藝術》 方騰飛

相關文章
相關標籤/搜索