《Java併發編程的藝術》之synchronized及JUC

synchrnoized的happens-before

int a;
boolean flag;
public synchronized void init(){// ①
    a = 100; // ②
    flag = true; // ③
}// ④
public synchronized void doTask(){ // ⑤
    if(flag){ // ⑥
        int result = a; // ⑦
    }
} // ⑧

假設線程A執行init(),線程B執行doTask(),有以下的happens-before關係:java

  • 根據程序次序規則:
    • ① hb ②
    • ② hb ③
    • ③ hb ④
    • ⑤ hb ⑥
    • ⑥ hb ⑦
    • ⑦ hb ⑧
  • 根據監視器規則:
    • ① hb ④
    • ④ hb ⑤
    • ⑤ hb ⑧

根據傳遞規則,保證init()方法全部的修改對doTask()方法可見,happens-before關係以下所示
緩存

一、2間的表示程序次序性規則,四、5間的表示監視器規則,因爲三、4有happens-before關係,四、5有happens-before關係,因此根據傳遞性規則,二、6間有happens-before關係。數據結構

線程A釋放鎖以前全部可見的共享變量,在線程B獲取同個鎖以後就變得可見了。app

synchrnoized的內存語義

synchrnoized獲取鎖內存語義

當釋放鎖時,JMM會把線程對應的本地內存中的共享變量刷新到主內存。以上面的例子爲例,共享數據的狀態示意圖以下所示
學習

synchrnoized釋放鎖內存語義

當A線程獲取到鎖時,JMM會把該線程對應的本地內存置爲無效。從而使得被監視器保護的臨界區代碼必須從主內存中讀取共享變量。線程

語義總結

對比鎖釋放-獲取的內存語義和volatile的寫-讀內存語義能夠看出:鎖釋放與volatile寫有相同的內存語義;鎖獲取和volatile讀有相同的內存語義。3d

  • 線程A釋放一個鎖,實質上是告訴其餘 要獲取該共享變量 線程 一個消息(線程A所作的修改)
  • 線程B獲取一個鎖,實質上是接受到其餘線程發出的消息(釋放這個鎖的線程對共享變量所作的修改)
  • 線程A釋放,隨後線程B獲取,實質上是線程A經過主內存給線程B發送消息。

這裏判斷語義是否相同是經過兩個操做的流程是否相同,好比線程A的鎖釋放完時,刷新至主內存;volatile寫完後,刷新至主內存,並通知其餘線程本地內存的共享變量失效(在鎖釋放環節裏是交給鎖獲取執行);code

CAS和JUC

synchronized是經過控制對象頭來控制鎖的升級,可是具體獲取鎖和釋放鎖的流程藏在JVM裏,這裏將經過ReentrantLock類比synchronized過程。對象

ReentrantLock的實現流程blog

這裏要學習的是CAS,JDK文檔對該方法的說明以下:若是當前狀態值等於預期值,則以原子方式將同步設置爲給定的更新值。此操做具備volatile讀和寫的語義。
前面講到volatile寫保證volatile寫不會和前面的操做發生重排序,volatile讀保證volatile讀不會和後面的操做發生重排序。組合這兩個條件就意味着同時實現了 禁止某一操做和操做前、操做後的重排序。CAS操做就是如此,它在是經過加上lock前綴來實現如下的功能:

  • 使用緩存鎖定保證原子性
  • 禁止以前和以後的重排序
  • 把寫緩衝區中的全部數據刷新到內存

正是由於CAS同時具備volatile讀和寫的內存語義,所以Java線程之間的通訊有下面四種方式。

  1. A線程寫volatile變量,B線程讀這個volatile變量
  2. A線程寫volatile變量,B線程用CAS修改volatile變量
  3. A線程用CAS修改volatile變量,B線程用CAS修改這個變量
  4. A線程用CAS修改volatile變量,B線程用volatile讀取該變量

JUC包的通用化的實現模式:

  • 聲明共享變量爲volatile
  • 使用CAS的原子條件來實現線程間的同步
  • 配合volatile讀/寫和CAS 來實習線程間的通訊

AQS,非阻塞數據結構和原子變量類,這些JUC包中的基礎都是使用上面的模式來實現的,而JUC包的高層類又是依賴這些基礎類來實現的。從總體看,JUC包的實現示意圖以下所示。

相關文章
相關標籤/搜索