在 多線程知識梳理(2) - synchronized 基本使用 中,咱們介紹了使用重量鎖來實現的synchronized
。今天,咱們就來一塊兒學習一下在JDK 1.6
以後,對synchronized
所採起的一系列優化措施。html
在介紹優化方法以前,咱們須要介紹兩個重要的概念Java
對象頭和Monitor
: 編程
在 Java&Android 基礎知識梳理(3) - 內存區域 中介紹內存區域的時候,對於一個Java
對象所佔的內存區域是這麼介紹的: 安全
32
位的虛擬機爲例,下圖就是鎖狀態標誌位所對應的數據結構含義:
Monitor
是線程私有的數據結構,因爲一個線程可能進入多個不一樣的同步方法,這些方法有可能會關聯到不一樣的Monitor
,所以每個線程都有一個可用的Monitor
列表,同時還有一個全局的可用列表,Monitor
數據結構包括如下成員變量:數據結構
Owner
:初始時爲空表示當前沒有任何線程擁有該Monitor
,當線程成功擁有該鎖後保存線程惟一標識,當鎖被釋放時又設置爲空。EntryQ
:關聯一個系統互斥鎖,阻塞全部試圖得到Monitor
可是最終失敗了的線程。RcThis
:表示blocked
或waiting
在該Monitor
上的全部線程的個數。Nest
:用來實現重入鎖的計數。HashCode
:保存從對象頭拷貝過來的HashCode
值。Candidate
:用來避免沒必要要的阻塞或等待線程喚醒,由於每一次只有一個線程可以成功擁有鎖,若是每次前一個釋放鎖的線程喚醒全部正在阻塞或等待的線程,會引發沒必要要的上下文切換(從阻塞到就緒而後由於競爭鎖失敗又被阻塞)從而致使性能嚴重降低。Candidate
只有兩種可能的值:0
表示沒有須要喚醒的線程,1
表示要喚醒一個繼任線程來競爭鎖。在JDK 1.6
以後,它對於鎖進行了一系列的優化措施,主要包括:自適應自旋鎖、鎖消除和鎖粗化。多線程
因爲線程的阻塞和喚醒須要CPU
從用戶態轉換成核心態,而頻繁的阻塞和喚醒對CPU
來講是一件負擔很重的工做。併發
所以,咱們在發現鎖已經被其它線程佔有時,並不直接讓當前線程進入阻塞狀態,而是讓線程執行一段無心義的循環,待循環結束後,如何仍然沒法獲取到鎖,那麼才進入阻塞狀態。性能
決定自旋鎖性能的關鍵在於自旋次數的選擇,在JDK 1.6
以後,引入了自適應自旋鎖,它會根據前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定新的自旋次數。學習
當JVM
檢測到不可能存在共享數據競爭,會對同步鎖進行鎖消除。優化
在使用同步鎖的時候,須要讓同步塊的做用範圍儘量地小,僅在共享數據的實際做用域中才進行同步,這樣作的目的是爲了使須要同步的操做數量儘量縮小,若是存在鎖競爭,那麼等待鎖的線程也能儘快拿到鎖。操作系統
然而,若是一系列連續加鎖解鎖操做,可能會致使沒必要要的性能損耗,因此有時能夠將多個連續的加鎖、解鎖操做鏈接在一塊兒,擴展成一個範圍更大的鎖。
在JDK 1.6
以前,鎖只有兩種狀態:無鎖狀態和重量級鎖狀態,而在這以後增長爲四種狀態:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,這種改進基於兩點考慮:
Mutex Lock
實現,操做系統實現線程之間的切換須要從用戶態切換到內核態,切換成本很高。須要注意,對於鎖的這四種狀態,它們會隨着競爭的激烈而逐漸升級,可是它只容許鎖升級,不容許鎖降級。
無鎖狀態和重量級鎖狀態都比較好理解,下面咱們主要介紹新增的兩種鎖狀態:偏向鎖狀態和輕量級鎖狀態。
整個轉換的流程圖以下所示,在後面的介紹中能夠參考:
引入偏向鎖的目的是:在無多線程競爭的狀況下,儘可能減小沒必要要的輕量級鎖執行路徑,它的理想狀況下是在無競爭時把整個同步都去掉,連CAS
操做都省略。
偏向鎖的意思是這個鎖會偏向於第一個得到它的線程,若是在接下來的執行過程當中,該鎖沒有被其它線程獲取,則持有偏向鎖的線程將永遠不須要再進行同步。
獲取偏向鎖的前提條件是synchronized
所修飾的對象處於可偏向狀態
01
1
當知足前提條件時,再去判斷對象的Mark Word
中的線程ID
是否指向當前線程
CAS
操做競爭鎖
Mark Word
的線程ID
替換爲當前線程ID
,接着執行同步代碼塊釋放偏向鎖的前提條件是其它的線程在競爭偏向鎖的過程當中出現了失敗的狀況,而且偏向鎖的釋放須要等待到達全局安全點。
當知足釋放偏向鎖的前提條件時,首先會暫停擁有偏向鎖的線程,接着判斷鎖對象是否處於被鎖定的狀態,決定鎖標誌位下一步的狀態:
01
,偏向鎖狀態置爲0
,表示它處於無鎖,且不可偏向狀態。00
,表示它處於被輕量級鎖定的狀態。引入輕量級鎖的目的是:在無多線程競爭的狀況下,減小傳統的重量級鎖使用操做系統互斥量產生的性能消耗。
獲取輕量級鎖的前提條件時當前對象處於無鎖狀態,
01
0
JVM
首先將在當前線程的棧幀中創建一個名爲鎖記錄(Lock Record
)的空間,用於存儲對象目前的Mark Word
的拷貝,以後JVM
利用CAS
操做嘗試將對象的Mark Word
更新爲指向Lock Record
的指針:
00
,表示處於鎖定的狀態,以後執行同步操做。Mark Word
是否指向當前線程的棧針10
,後面等待的線程將會進入阻塞狀態。輕量級鎖的釋放也是經過CAS
操做來進行的:
Displaced Mark Word
中的數據。CAS
操做將取出的數據替換到當前對象的Mark Word
中:對於輕量級鎖,它性能提高的依據是默認"對於絕大部分的鎖,在整個生命週期內是不會存在競爭的",若是不符合這種狀況,那麼除了互斥的開銷外,還有額外的CAS
操做,這樣輕量級鎖比重量級鎖更慢。
Java 併發編程:Synchronized 底層優化(偏向鎖、輕量級鎖) 死磕 Java 併發 -----深刻分析 synchronized 的實現原理