Synchronize--實現原理

轉載自:https://blog.csdn.net/u012715840/article/details/58247556

鎖的數據結構

同步代碼塊是使用monitorenter和monitorexit指令實現的,任何java對象都有一個monitor與之關聯,當一個monitor被持有後,對象就處於鎖定狀態。

è¿éåå¾çæè¿°

è¿éåå¾çæè¿°

在運行期間,Mard Word裏存儲的數據會隨着鎖標誌位的變化而變化。Mark Word可能變化爲存儲以下數據結構。

è¿éåå¾çæè¿°

自旋鎖

       通常我們稱Sychronized鎖是一種重量級鎖,是因爲在互斥狀態下,沒有得到鎖的線程會被掛起阻塞,而掛起線程和恢復線程的操作都需要轉入內核態中完成。同時,虛擬機開發團隊也注意到,許多應用上的數據鎖只會持續很多的一段時間,如果爲了這段時間去掛起和恢復線程是不值得的,所以引入了自旋鎖。 
  自旋鎖與互斥鎖有點類似,只是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環在那裏看是 否該自旋鎖的保持者已經釋放了鎖,”自旋”一詞就是因此而得名。其作用是爲了解決某項資源的互斥使用。因爲自旋鎖不會引起調用者睡眠,所以自旋鎖的效率遠 高於互斥鎖。雖然它的效率比互斥鎖高,但是它也有些不足之處: 
  自旋鎖一直佔用CPU,他在未獲得鎖的情況下,一直運行--自旋,所以佔用着CPU,如果不能在很短的時 間內獲得鎖,這無疑會使CPU效率降低。 
  自旋鎖在JDK6以後已經默認開啓,可以通過-XX:+UseSpinning參數來開啓。 
  但這顯然並不是最好的一種方法,不掛起線程的代價就是該線程會一直佔用處理器。如果鎖被佔用的時間很短,自旋等待的效果就會很好,反之,自旋會消耗大量處理器資源。因此,自旋的等待時間必須有一定的限度,如果超過限度還沒有獲得鎖,就要掛起線程,這個限度默認是10次,可以使用-XX:PreBlockSpin改變。 
     在JDK6以後又引入了自適應自旋鎖,也就說自旋的時間限度不是一個固定值了,而是由上一次同一個鎖的自旋時間及鎖的擁有者狀態來決定。虛擬機認爲,如果同一個鎖對象自旋剛剛成功獲得鎖,那麼下一次很可能獲得鎖,所以允許這次自旋鎖自旋很長時間、而如果某個鎖很少獲得鎖,那麼以後在獲取鎖的過程中可能忽略到自旋過程。

鎖升級

        在Java 6中爲了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”,在Java中,鎖共有4種狀態,級別從低到高依次爲:無狀態鎖,偏向鎖,輕量級鎖和重量級鎖狀態,這幾個狀態會隨着競爭情況逐漸升級。鎖可以升級但不能降級。  

輕量級鎖

         “輕量級”是相對於使用操作系統互斥量來實現的傳統鎖而言的。但是,首先需要強調一點的是,輕量級鎖並不是用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用產生的性能消耗。在解釋輕量級鎖的執行過程之前,先明白一點,輕量級鎖所適應的場景是線程交替執行同步塊的情況,如果存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹爲重量級鎖。

輕量級鎖的加鎖過程

        在代碼進入同步塊的時候,如果同步對象鎖狀態爲無鎖狀態(鎖標誌位爲“01”狀態,是否爲偏向鎖爲“0”),虛擬機首先將在當前線程的棧幀中建立一個名爲鎖記錄(Lock 
Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝,官方稱之爲 Displaced Mark 
Word。這時候線程堆棧與對象頭的狀態如圖一所示。
拷貝對象頭中的Mark Word複製到鎖記錄中。
拷貝成功後,虛擬機將使用CAS操作嘗試將對象的Mark Word更新爲指向Lock Record的指針,並將Lock record裏的owner指針指向object mark word。如果更新成功,則執行步驟(4),否則執行步驟(5)。
如果這個更新動作成功了,那麼這個線程就擁有了該對象的鎖,並且對象Mark 
Word的鎖標誌位設置爲“00”,即表示此對象處於輕量級鎖定狀態,這時候線程堆棧與對象頭的狀態如圖二所示。
如果這個更新操作失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是就說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行。否則說明多個線程競爭鎖,輕量級鎖就要膨脹爲重量級鎖,鎖標誌的狀態值變爲“10”,Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,後面等待鎖的線程也要進入阻塞狀態。而當前線程便嘗試使用自旋來獲取鎖,自旋就是爲了不讓線程阻塞,而採用循環去獲取鎖的過程。
圖1— 輕量級鎖CAS操作之前堆棧與對象的狀態 

è¿éåå¾çæè¿°
圖2— 輕量級鎖CAS操作之後堆棧與對象的狀態 

è¿éåå¾çæè¿°
輕量級鎖的解鎖過程

        通過CAS操作嘗試把線程中複製的Displaced Mark Word對象替換當前的Mark Word。
如果替換成功,整個同步過程就完成了。
如果替換失敗,說明有其他線程嘗試過獲取該鎖(此時鎖已膨脹),那就要在釋放鎖的同時,喚醒被掛起的線程。

è¿éåå¾çæè¿°

è¿éåå¾çæè¿°
偏向鎖

        引入偏向鎖是爲了在無多線程競爭的情況下儘量減少不必要的輕量級鎖執行路徑,因爲輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令(由於一旦出現多線程競爭的情況就必須撤銷偏向鎖,所以偏向鎖的撤銷操作的性能損耗必須小於節省下來的CAS原子指令的性能消耗)。上面說過,輕量級鎖是爲了在線程交替執行同步塊時提高性能(通過自旋鎖,使無法獲得鎖的線程無需立即進入阻塞狀態,而是在一定時間內循環以獲得鎖,減少掛起線程和恢復線程帶來的消耗),而偏向鎖則是在只有一個線程執行同步塊時進一步提高性能(在無多線程競爭情況下,獲得鎖的線程不釋放鎖,以減少CAS操作)。

偏向鎖獲取過程

        訪問Mark Word中偏向鎖的標識是否設置成1,鎖標誌位是否爲01——確認爲可偏向狀態。
如果爲可偏向狀態,則測試線程ID是否指向當前線程,如果是,進入步驟(5),否則進入步驟(3)。
如果線程ID並未指向當前線程,則通過CAS操作競爭鎖。如果競爭成功,則將Mark 
Word中線程ID設置爲當前線程ID,然後執行(5);如果競爭失敗,執行(4)。
如果CAS獲取偏向鎖失敗,則表示有競爭。當到達全局安全點(safepoint)時獲得偏向鎖的線程被掛起,偏向鎖升級爲輕量級鎖,然後被阻塞在安全點的線程繼續往下執行同步代碼。
執行同步代碼。


偏向鎖的釋放

        偏向鎖的撤銷在上述第四步驟中有提到。偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖,線程不會主動去釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有字節碼正在執行),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處於被鎖定狀態,撤銷偏向鎖後恢復到未鎖定(標誌位爲“01”)或輕量級鎖(標誌位爲“00”)的狀態。 è¿éåå¾çæè¿°

重量級鎖、輕量級鎖和偏向鎖之間轉換

è¿éåå¾çæè¿°
三種鎖的優缺點比較

優點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執行非同步方法比僅存在納秒級的差距 如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗 適用於只有一個線程訪問同步塊場景
輕量級鎖 競爭的線程不會阻塞,提高了程序的響應速度 如果始終得不到鎖競爭的線程使用自旋會消耗CPU 追求響應時間,鎖佔用時間很短
重量級鎖 線程競爭不使用自旋,不會消耗CPU 線程阻塞,響應時間緩慢 追求吞吐量,鎖佔用時間較長

重量級鎖不使用自旋,獲取鎖失敗的線程直接進入阻塞狀態 
輕量級鎖使用自旋

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章