synchronize底層實現原理


    相信對Java程序員來說,synchronized關鍵字對大家來說並不陌生,當我們遇到並發情況時,優先會想到用synchronized關鍵字去解決,synchronized確實能夠幫助我們去解決並發的問題,但是它會引起一些其他問題,比如最突出的一點就是程序效率問題,不過后面隨着JDK1.6對synchronized關鍵字做出了許多優化,讓synchronizedjava.util.concurrent.locks.ReentrantLock等並發包中的類效率差不多。下面主要來分析一下synchronized底層的實現原理


1 基本使用

    synchronized關鍵字可以用來修飾三個地方:

1.synchronized放在實例方法上,鎖對象是當前的this對象
2.synchronized放在類方法上,也就是我們所說的靜態方法上,鎖對象是方法區中的類對象,是一個全局鎖
3.synchronized修飾代碼塊,也就是synchronized(object){},鎖對象是()中的對象

synchronized關鍵字用來修飾的位置不同,其實現原理也是不同的。鎖住的對象也是不同的。在Java中,每個對象里面隱式的存在一個叫monitor(對象監視器)的對象,這個對象源碼是采用C++實現的,下面來看一下Monitor對象的源碼:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; // 記錄個數
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; // 處於wait狀態的線程,會被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; // 處於等待鎖block狀態的線程,會被加入到該列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

當monitor對象被線程持有時,Monitor對象中的count就會進行+1,當線程釋放monitor對象時,count又會進行-1操作。用count來表示monitor對象是否被持有


2 同步原理

    針對synchronized修飾的地方不同,實現的原理不同

1.先看synchronized放在實例方法上的代碼:

public class Test {

  public synchronized void test() {
  }
}

用javap -verbose查看反編譯結果:

從反編譯的結果來看,我們可以看到test()方法中多了一個標識符。JVM就是根據該ACC_SYNCHRONIZED標識符來實現方法的同步:

當方法被執行時,JVM調用指令會去檢查方法上是否設置了ACC_SYNCHRONIZED標識符,如果設置了ACC_SYNCHRONIZED標識符,則會獲取鎖對象的monitor對象,線程執行完方法體后,又會釋放鎖對象的monitor對象。在此期間,其他線程無法獲得鎖對象的monitor對象

2.第二種情況,synchronized放在類方法上:

public class Test {

    public synchronized static void test(){
    }
}

反編譯結果:

我們可以看到跟放在實例方法相同,也是test()方法上會多一個標識符。可以得出synchronized放在實例方法上和放在類方法上的實現原理相同,都是ACC_SYNCHRONIZED標識符去實現的。只是它們鎖住的對象不同

3.第三種情況,synchronized修飾代碼塊:

public class Test {

    public void test(){
        synchronized (this) {
        }
    }
}

反編譯結果:

我們可以看到test()字節碼指令中會有兩個monitorenter和monitorexit指令,

(1)monitorenter: monitorenter指令表示獲取鎖對象的monitor對象,這是monitor對象中的count並會加+1,如果monitor已經被其他線程所獲取,該線程會被阻塞住,直到count=0,再重新嘗試獲取monitor對象

(2)monitorexit: monitorexit與monitorenter是相對的指令,表示進入和退出。執行monitorexit指令表示該線程釋放鎖對象的monitor對象,這時monitor對象的count便會-1變成0,其他被阻塞的線程可以重新嘗試獲取鎖對象的monitor對象


synchronized放置的位置不同可以得出,synchronized用來修飾方法時,是通過ACC_SYNCHRONIZED標識符來保持線程同步的。而用來修飾代碼塊時,是通過monitorentermonitorexit指令來完成


3 同步概念

3.1 Java對象頭

    像我們上面所提到的monitor監視器對象存在於Java對象的對象頭Mark Word中,

什么是Java對象頭的Mark Word了?這就跟Java對象在JVM中的內存布局有關系,Java對象在JVM內存中分為三塊區域:對象頭,實例數據和對齊填充

1. 對象頭:對象頭又包含以下部分:Mark Word, 類型指針,如果對象是數組的話,還會存在一個數據數組長度,用來記錄數據長度。其中Mark Word又包含:哈希碼(HashCode),GC分代年齡,鎖狀態標志,線程持有的鎖等信息。
2. 實例數據:這部分是對象真正存儲的有效信息,也就是我們在程序中所定義的各種類型的字段內容。
3. 對齊填充:這部分數據並不是必然存在的,JVM內存管理系統要求我們定義的對象大小必須是8字節的整數倍,如果對象大小不是整數倍,這時就會有對齊填充這塊數據,來將對象大小補全成為8字節的整數倍

如果所示:

JDK對synchronized關鍵字的優化都是基於對象頭中的Mark Word進行優化的,當對象被不同數量線程去競爭時,這是對象的鎖狀態會發生改變,來標識這時對象處於一個無鎖,偏向鎖,輕量級鎖和重量級鎖狀態。


下面結合鎖的優化,來講一下Mark Word中的鎖狀態發生的變化:

  1. 無鎖

    也就是代表對象的monitor對象並沒有被線程所持有,代表的是對象處於無鎖狀態

  2. 偏向鎖

    偏向鎖是JDK1.6后面引起的一項鎖優化技術,在無鎖競爭的情況下,一個線程通過一次CAS操作來嘗試將對象頭中的Thread ID字段設置為自己的線程號,如果設置成功,則獲得鎖,那么以后線程再次進入和退出同步塊時,就不需要使用CAS來獲取鎖,只是簡單的測試一個對象頭中的Mark Word字段中是否存儲 着指向當前線程的偏向鎖。如果使用CAS設置失敗時,說明存在鎖的競爭,這時偏向鎖便會升級成輕量級鎖和重量級鎖。偏向鎖指的是這個鎖會偏向於第一個獲得它的線程。

    下面來看一下對象在無鎖和偏向鎖狀態下,Mark Word的鎖狀態位變化:

    我們可以看到這是偏向鎖的標志位被至為了1,表示現在處於偏向模式

3.輕量級鎖

​ 表示線程通過一定的數量CAS操作(JDK1.6后默認10次)完成加鎖和解鎖操作,如果鎖獲取失敗,會通過自旋來獲取,競爭的線程不會阻塞,如果還是獲取失敗,表示此時存在其他線程競爭鎖(兩條或兩條以上的線程競爭同一個鎖),則輕量級鎖會膨脹成重量級鎖。

4.重量級鎖

​ 當一個鎖被兩條或兩條以上的線程競爭的時候,這時候輕量級鎖就會演變成重量級鎖。

在輕量級鎖和重量級鎖的情況下,鎖狀態標記位:

    除了以上幾種鎖外,其實還有自旋鎖,自適應自旋鎖

5.自旋鎖

​ 當兩個線程去競爭同一把鎖時,一個線程獲取成功,一個線程獲取失敗,這時可能會出現獲取成功的線程持有鎖的時間非常短,如果這時候將獲取失敗的線程進行掛起的話,會造成功線程上下文的切換,到時候又需要喚醒線程,這時我們可以讓獲取失敗的線程進行一個自旋,無需將線程掛起。等到鎖釋放。但是這種方案適用於鎖被占用的時間很短的情況,如果鎖被持有的時間很長,然后線程將會一直處於自旋狀態,白白消耗處理器資源。自旋等待的時間必須要有一定的限度,如果超過此數還是沒有獲取到,則將線程掛起。自旋此數的默認值是10次,可以使用JVM參數-XX:PreBlockSpin來進行更改。

6.自適應自旋鎖

​ 自適應自旋鎖是在自旋鎖的基礎上產生的,進行了一次優化。自適應意味着自旋的時間不再固定,而是由前一次在同一個鎖上的自旋時間幾鎖的擁有者的狀態來決定的。


鎖的優缺點對比

結論:

    從上面分析得出:synchronized關鍵字底層實現主要是通過monitor對象去完成的,修飾的位置不同,實現的方式有點差異,但是底層都是通過monitor對象去實現的。后面還講了JDK1.6對synchronized關鍵字做出的鎖優化。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM