Java核心知識 多線程併發 Synchronized 同步鎖(九)


Java核心知識 多線程併發 Synchronized 同步鎖(九)

Synchronized 同步鎖

synchronized 它可以把任意一個非 NULL 的對象當作鎖。他屬於獨佔式的悲觀鎖,同時屬於可重 入鎖。

Synchronized 作用範圍

1. 作用於方法時,鎖住的是對象的實例(this);

2. 當作用於靜態方法時,鎖住的是 Class 實例,又因為 Class 的相關數據存儲在永久帶 PermGen (jdk1.8 則是 metaspace),永久帶是全局共享的,因此靜態方法鎖相當於類的一個全局鎖, 會鎖所有調用該方法的線程;

3. synchronized 作用於一個對象實例時,鎖住的是所有以該對象為鎖的代碼塊。它有多個隊列, 當多個線程一起訪問某個對象監視器的時候,對象監視器會將這些線程存儲在不同的容器中。

Synchronized 核心組件

1) Wait Set:哪些調用 wait 方法被阻塞的線程被放置在這裡;

2) Contention List:競爭隊列,所有請求鎖的線程首先被放在這個競爭隊列中;

3) Entry List:Contention List 中那些有資格成為候選資源的線程被移動到 Entry List 中; 4) OnDeck:任意時刻,最多隻有一個線程正在競爭鎖資源,該線程被成為 OnDeck;

5) Owner:當前已經獲取到所資源的線程被稱為 Owner;

6) !Owner:當前釋放鎖的線程。

Synchronized 實現


Java核心知識 多線程併發 Synchronized 同步鎖(九)

1. JVM 每次從隊列的尾部取出一個數據用於鎖競爭候選者( OnDeck),但是併發情況下, ContentionList 會被大量的併發線程進行 CAS 訪問,為了降低對尾部元素的競爭,JVM 會將 一部分線程移動到 EntryList 中作為候選競爭線程。

2. Owner 線程會在 unlock 時,將 ContentionList 中的部分線程遷移到 EntryList 中,並指定 EntryList 中的某個線程為 OnDeck 線程(一般是最先進去的那個線程)。

3. Owner 線 程 並 不 直 接 把 鎖 傳遞給 OnDeck 線 程 , 而 是 把 鎖 競 爭 的 權 利 交 給 OnDeck, OnDeck 需要重新競爭鎖。這樣雖然犧牲了一些公平性,但是能極大的提升系統的吞吐量,在 JVM 中,也把這種選擇行為稱之為“競爭切換”。

4. OnDeck 線程獲取到鎖資源後會變為 Owner 線程,而沒有得到鎖資源的仍然停留在 EntryList 中。如果 Owner 線程被 wait 方法阻塞,則轉移到 WaitSet 隊列中,直到某個時刻通過 notify 或者 notifyAll 喚醒,會重新進去 EntryList 中。

5. 處於 ContentionList、EntryList、WaitSet 中的線程都處於阻塞狀態,該阻塞是由操作系統 來完成的(Linux 內核下采用 pthread_mutex_lock 內核函數實現的)。

6. Synchronized 是非公平鎖。 Synchronized 在線程進入 ContentionList 時,等待的線程會先 嘗試自旋獲取鎖,如果獲取不到就進入 ContentionList,這明顯對於已經進入隊列的線程是 不公平的,還有一個不公平的事情就是自旋獲取鎖的線程還可能直接搶佔 OnDeck 線程的鎖 資源。

7. 每個對象都有個 monitor 對象,加鎖就是在競爭 monitor 對象,代碼塊加鎖是在前後分別加 上 monitorenter 和 monitorexit 指令來實現的,方法加鎖是通過一個標記位來判斷的

8. synchronized 是一個重量級操作,需要調用操作系統相關接口,性能是低效的,有可能給線 程加鎖消耗的時間比有用操作消耗的時間更多。

9. Java1.6,synchronized 進行了很多的優化,有適應自旋、鎖消除、鎖粗化、輕量級鎖及偏向 鎖等,效率有了本質上的提高。在之後推出的 Java1.7 與 1.8 中,均對該關鍵字的實現機理做 了優化。引入了偏向鎖和輕量級鎖。都是在對象頭中有標記位,不需要經過操作系統加鎖。

10. 鎖可以從偏向鎖升級到輕量級鎖,再升級到重量級鎖。這種升級過程叫做鎖膨脹;

11. JDK 1.6 中默認是開啟偏向鎖和輕量級鎖,可以通過-XX:-UseBiasedLocking 來禁用偏向鎖。


分享到:


相關文章: