鎖是Java并發(fā)編程中最重要的同步機制.鎖除了讓臨界區(qū)互斥執(zhí)行外,還可以讓釋放鎖的線程向獲取同一個鎖的線程發(fā)送信息.
上述happens-before關系的圖形化表示形式如下圖所示
每一個箭頭鏈接的兩個節(jié)點,代表了一個happens-before關系。黑色箭頭表示程序順序規(guī)則;橙色箭頭表示監(jiān)視器鎖規(guī)則;藍色箭頭表示組合這些規(guī)則后提供的happens-before保證。
上圖中在線程A釋放了鎖之后,隨后線程B獲取同一個鎖。在上圖職工2 happens-before 5,因此,線程A在釋放鎖之前所有可見的共享變量,在線程B獲取同一個鎖之后,將立刻變得對線程B可見。
當線程釋放鎖時,JMM會把該線程對應的本地內(nèi)存的共享變量刷新到主內(nèi)存.以上面的MonitorExample程序為例,A線程釋放鎖后,共享數(shù)據(jù)的狀態(tài)示意圖如圖下圖所示。
當線程獲取鎖時,JMM會把該線程所對應的本地內(nèi)存置為無效.從而使得被監(jiān)視器保護的臨界代碼必須從主內(nèi)存中讀取共享變量.
對鎖釋放和鎖獲取的內(nèi)存語義小結(jié)
示例代碼
使用公平鎖時,加鎖方法lock()調(diào)用軌跡如下。
1)ReentrantLock:lock()。
2)FairSync:lock()。
3)AbstractQueuedSynchronizer:acquire(int arg)。
4)ReentrantLock:tryAcquire(int acquires)。
在第4步真正開始加鎖,下面是該方法的源代碼。
公平鎖在釋放鎖的最后寫volatile變量state,在獲取鎖時首先讀這個volatile變量.根據(jù)volatile變量的happens-before規(guī)則,釋放鎖的線程寫在volatile變量之前可見的共享變量,在獲取鎖的線程讀取同一個volatile變量后立即變得對獲取鎖的線程可見.
非公平鎖的釋放和公平鎖完全一樣,所以這里僅僅分析非公平鎖的獲取。使用非公平鎖時,加鎖方法lock()調(diào)用軌跡如下。
1)ReentrantLock:lock()。
2)NonfairSync:lock()。
3)AbstractQueuedSynchronizer:compareAndSetState(int expect,int update)。
在第3步真正開始加鎖,下面是該方法的源代碼。
編譯器不會對volatile讀與volatile讀后面的任意內(nèi)存操作重排序;編譯器不會對volatile寫與volatile寫前面的任意內(nèi)存操作重排序。組合這兩個條件,意味著為了同時實現(xiàn)volatile讀和volatile寫的內(nèi)存語義,編譯器不能對CAS與CAS前面和后面的任意內(nèi)存操作重排序.
公平鎖和非公平鎖的內(nèi)存語義小結(jié)
·公平鎖和非公平鎖釋放時,最后都要寫一個volatile變量state。
·公平鎖獲取時,首先會去讀volatile變量。
·非公平鎖獲取時,首先會用CAS更新volatile變量,這個操作同時具有volatile讀和volatile寫的內(nèi)存語義。
鎖釋放-獲取的內(nèi)存語義的實現(xiàn)至少有
下面兩種方式
由于Java的CAS同時具有volatile讀和volatile寫的內(nèi)存語義,因此Java線程之間的通信現(xiàn)在有了下面4種方式。
Java的CAS會使用現(xiàn)代處理器上提供的高效機器級別的原子指令,這些原子指令以原子方式對內(nèi)存執(zhí)行讀-改-寫操作,這是在多處理器中實現(xiàn)同步的關鍵(從本質(zhì)上來說,能夠支持原子性讀-改-寫指令的計算機,是順序計算圖靈機的異步等價機器,因此任何現(xiàn)代的多處理器都會去支持某種能對內(nèi)存執(zhí)行原子性讀-改-寫操作的原子指令)。同時,volatile變量的讀/寫和CAS可以實現(xiàn)線程之間的通信。把這些特性整合在一起,就形成了整個concurrent包得以實現(xiàn)的基石。如果我們仔細分析concurrent包的源代碼實現(xiàn),會發(fā)現(xiàn)一個通用化的實現(xiàn)模式。
AQS,非阻塞數(shù)據(jù)結(jié)構(gòu)和原子變量類(java.util.concurrent.atomic包中的類),這些concurrent包中的基礎類都是使用這種模式來實現(xiàn)的,而concurrent包中的高層類又是依賴于這些基礎類來實現(xiàn)的
參考書籍:<