深度琢磨Java 併發編程 到底啥是同步機制 如何盤他

 

1、 同步程序員

1 synchronized 關鍵字編程

synchronized 鎖什麼?鎖對象。緩存

可能鎖對象包括: this, 臨界資源對象,Class 類對象。多線程

1.1 同步方法併發

synchronized T methodName(){}

同步方法鎖定的是當前對象。當多線程經過同一個對象引用屢次調用當前同步方法時,需同步執行。優化

1.2 同步代碼塊this

同步代碼塊的同步粒度更加細緻,是商業開發中推薦的編程方式。能夠定位到具體的同步位置,而不是簡單的將方法總體實現同步邏輯。在效率上,相對更高。atom

1.2.1 鎖定臨界對象線程

T methodName(){ 
synchronized(object){} 
}

同步代碼塊在執行時,是鎖定 object 對象。當多個線程調用同一個方法時,鎖定對象不變的狀況下,需同步執行。指針

1.2.2 鎖定當前對象

T methodName(){ 
synchronized(this){} 
}

當鎖定對象爲 this 時,至關於 1.1 同步方法。

1.3 鎖的底層實現

Java 虛擬機中的同步(Synchronization)基於進入和退出管程(Monitor)對象實現。同步方法 並非由 monitor enter monitor exit 指令來實現同步的,而是由方法調用指令讀取運行時常量池中方法的 ACC_SYNCHRONIZED 標誌來隱式實現的。

1.3.1 對象內存簡圖

 
 
 

 

對象頭:存儲對象的 hashCode、鎖信息或分代年齡或 GC 標誌,類型指針指向對象的類元數據,JVM 經過這個指針肯定該對象是哪一個類的實例等信息。

實例變量:存放類的屬性數據信息,包括父類的屬性信息

填充數據:因爲虛擬機要求對象起始地址必須是 8 字節的整數倍。填充數據不是必須存在的,僅僅是爲了字節對齊

當在對象上加鎖時,數據是記錄在對象頭中。當執行 synchronized 同步方法或同步代碼塊時,會在對象頭中記錄鎖標記,鎖標記指向的是 monitor 對象(也稱爲管程或監視器鎖)的起始地址。每一個對象都存在着一個 monitor 與之關聯,對象與其 monitor 之間的關係有存在多種實現方式,如 monitor 能夠與對象一塊兒建立銷燬或當線程試圖獲取對象鎖時自動生成,但當一個 monitor 被某個線程持有後,它便處於鎖定狀態。

Java 虛擬機(HotSpot)中,monitor 是由 ObjectMonitor 實現的。

ObjectMonitor 中有兩個隊列,_WaitSet _EntryList,以及_Owner 標記。其中_WaitSet 是用於管理等待隊列(wait)線程的,_EntryList 是用於管理鎖池阻塞線程的,_Owner 標記用於記錄當前執行線程。線程狀態圖以下:

 
 
 

 

當多線程併發訪問同一個同步代碼時,首先會進入_EntryList,當線程獲取鎖標記後, monitor 中的_Owner 記錄此線程,並在 monitor 中的計數器執行遞增計算(+1),表明鎖定,其餘線程在_EntryList 中繼續阻塞。若執行線程調用 wait 方法,則 monitor 中的計數器執行賦值爲 0 計算,並將_Owner 標記賦值爲 null,表明放棄鎖,執行線程進如_WaitSet 中阻塞。

若執行線程調用 notify/notifyAll 方法,_WaitSet 中的線程被喚醒,進入_EntryList 中阻塞,等待獲取鎖標記。若執行線程的同步代碼執行結束,一樣會釋放鎖標記,monitor 中的_Owner 標記賦值爲 null,且計數器賦值爲 0 計算。

1.4 鎖的種類

Java 中鎖的種類大體分爲偏向鎖,自旋鎖,輕量級鎖,重量級鎖。

鎖的使用方式爲:先提供偏向鎖,若是不知足的時候,升級爲輕量級鎖,再不知足,升級爲重量級鎖。自旋鎖是一個過渡的鎖狀態,不是一種實際的鎖類型。

鎖只能升級,不能降級。

1.4.1 重量級鎖

1.3 中解釋的就是重量級鎖。

1.4.2 偏向鎖

是一種編譯解釋鎖。若是代碼中不可能出現多線程併發爭搶同一個鎖的時候,JVM 編譯代碼,解釋執行的時候,會自動的放棄同步信息。消除 synchronized 的同步代碼結果。使用鎖標記的形式記錄鎖狀態。在 Monitor 中有變量 ACC_SYNCHRONIZED。當變量值使用的時候,表明偏向鎖鎖定。能夠避免鎖的爭搶和鎖池狀態的維護。提升效率。

1.4.3 輕量級鎖

過渡鎖。當偏向鎖不知足,也就是有多線程併發訪問,鎖定同一個對象的時候,先提高爲輕量級鎖。也是使用標記 ACC_SYNCHRONIZED 標記記錄的。ACC_UNSYNCHRONIZED 標記記錄未獲取到鎖信息的線程。就是隻有兩個線程爭搶鎖標記的時候,優先使用輕量級鎖。

兩個線程也可能出現重量級鎖。

1.4.4 自旋鎖

是一個過渡鎖,是偏向鎖和輕量級鎖的過渡。

當獲取鎖的過程當中,未獲取到。爲了提升效率,JVM 自動執行若干次空循環,再次申請鎖,而不是進入阻塞狀態的狀況。稱爲自旋鎖。自旋鎖提升效率就是避免線程狀態的變動。

2 volatile 關鍵字

變量的線程可見性。在 CPU 計算過程當中,會將計算過程須要的數據加載到 CPU 計算緩存中,當 CPU 計算中斷時,有可能刷新緩存,從新讀取內存中的數據。在線程運行的過程當中,若是某變量被其餘線程修改,可能形成數據不一致的狀況,從而致使結果錯誤。而 volatile 修飾的變量是線程可見的,當 JVM 解釋 volatile 修飾的變量時,會通知 CPU,在計算過程當中,每次使用變量參與計算時,都會檢查內存中的數據是否發生變化,而不是一直使用 CPU 緩存中的數據,能夠保證計算結果的正確。

volatile 只是通知底層計算時,CPU 檢查內存數據,而不是讓一個變量在多個線程中同步。

3 wait&notify

 

4 AtomicXxx 類型組

原子類型。

concurrent.atomic 包中定義了若干原子類型,這些類型中的每一個方法都是保證了原子操做的。多線程併發訪問原子類型對象中的方法,不會出現數據錯誤。在多線程開發中,若是某數據須要多個線程同時操做,且要求計算原子性,能夠考慮使用原子類型對象。

注意:原子類型中的方法是保證了原子操做,但多個方法之間是沒有原子性的。如:

AtomicInteger i = new AtomicInteger(0); if(i.get() != 5) i.incrementAndGet();

在上述代碼中,get 方法和incrementAndGet 方法都是原子操做,但複合使用時,沒法

保證原子性,仍舊可能出現數據錯誤。

5 CountDownLatch 門閂

門閂是 concurrent 包中定義的一個類型,是用於多線程通信的一個輔助類型。

門閂至關於在一個門上加多個鎖,當線程調用 await 方法時,會檢查門閂數量,若是門閂數量大於 0,線程會阻塞等待。當線程調用 countDown 時,會遞減門閂的數量,當門閂數量爲 0 時,await 阻塞線程可執行。

6 鎖的重入

Java 中,同步鎖是能夠重入的。只有同一線程調用同步方法或執行同步代碼塊,對同一個對象加鎖時纔可重入。

當線程持有鎖時,會在 monitor 的計數器中執行遞增計算,若當前線程調用其餘同步代碼,且同步代碼的鎖對象相同時,monitor 中的計數器繼續遞增。每一個同步代碼執行結束, monitor 中的計數器都會遞減,直至全部同步代碼執行結束,monitor 中的計數器爲 0 時,釋放鎖標記,_Owner 標記賦值爲 null

7 ReentrantLock

重入鎖,建議應用的同步方式。相對效率比 synchronized 高。量級較輕。

synchronized JDK1.5 版本開始,嘗試優化。到 JDK1.7 版本後,優化效率已經很是好了。

在絕對效率上,不比 reentrantLock 差多少。

使用重入鎖,必須必須必須手工釋放鎖標記。通常都是在 finally 代碼塊中定義釋放鎖標記的 unlock 方法。

7.1 公平鎖

 
 
 

 

 

8 ThreadLocal

 
 
 

 

 
 
 

你說啥?你想要視頻教程文字看不懂?你還想要其餘視頻教程?那你趕忙關注公衆號程序員理想吧

相關文章
相關標籤/搜索