天天看點

Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念

Java3yのsynchronized

一、 Monitor 概念

1、 Java 對象頭

(重點)

對象頭

對象頭包含兩部分:

運作時中繼資料(Mark Word)

類型指針 (Klass Word)

  1. 運作時中繼資料

    • 哈希值(HashCode)

      ,可以看作是堆中對象的位址
    • GC分代年齡(年齡計數器)

      (用于新生代from/to區晉升老年代的标準, 門檻值為15)
    • 鎖狀态标志 (用于JDK1.6對synchronized的優化 -> 輕量級鎖)
    • 線程持有的鎖
    • 偏向線程ID (用于JDK1.6對synchronized的優化 -> 偏向鎖)
    • 偏向時間戳
  2. 類型指針

    • 指向

      類中繼資料InstanceKlass,确定該對象所屬的類型

      。指向的其實是方法區中存放的類元資訊

說明:如果對象是數組,還需要記錄數組的長度

  • 以 32 位虛拟機為例,普通對象的對象頭結構如下,其中的

    Klass Word

    類型指針

    ,指向

    方法區

    對應的

    Class對象

Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 數組對象
    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 其中 Mark Word 結構為:

    無鎖(001)、偏向鎖(101)、輕量級鎖(00)、重量級鎖(10)

    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 是以一個對象的結構如下:
    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念

2、 Monitor 原理 (Synchronized底層實作-重量級鎖)

多線程同時通路臨界區: 使用重量級鎖
  • JDK6對Synchronized的優先狀态:

    偏向鎖–>輕量級鎖–>重量級鎖

  • Monitor

    被翻譯為

    螢幕

    或者

    管程

每個Java對象

都可以

關聯一個(作業系統的)Monitor

,如果使用

synchronized

對象上鎖(重量級)

,該

對象頭的MarkWord

中就被設定為

指向Monitor對象

指針

下圖原了解釋:
  • Thread1

    通路到

    synchronized(obj)

    中的

    共享資源

    的時候
    • 首先會将synchronized中的

      鎖對象

      對象頭

      MarkWord

      去嘗試指向

      作業系統

      Monitor

      對象. 讓鎖對象中的MarkWord和Monitor對象相關聯. 如果關聯成功, 将obj對象頭中的

      MarkWord

      對象狀态

      從01改為10。
    • 因為Monitor沒有和其他的obj的MarkWord相關聯, 是以

      Thread1

      就成為了該

      Monitor

      的Owner(所有者)。
    • 又來了個

      Thread1

      執行synchronized(obj)代碼, 它首先會看看能不能執行該

      臨界區

      的代碼; 它會檢查obj是否關聯了Montior, 此時已經有關聯了, 它就會去看看該Montior有沒有所有者(Owner), 發現有所有者了(Thread2);

      Thread1

      也會和該Monitor關聯, 該線程就會進入到它的

      EntryList(阻塞隊列)

      ;
    • Thread2

      執行完

      臨界區

      代碼後, Monitor的

      Owner(所有者)

      就空出來了. 此時就會

      通知

      Monitor中的EntryList阻塞隊列中的線程, 這些線程通過

      競争

      , 成為新的

      所有者

      Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 剛開始時

    Monitor

    中的

    Owner為null

  • 當Thread-2 執行synchronized(obj){}代碼時就會将Monitor的所有者Owner 設定為 Thread-2,上鎖成功,Monitor中同一時刻隻能有一個Owner
  • 當Thread-2 占據鎖時,如果線程Thread-3,Thread-4也來執行synchronized(obj){}代碼,就會進入

    EntryList

    中變成

    BLOCKED狀态

  • Thread-2 執行完同步代碼塊的内容,然後喚醒 EntryList 中等待的線程來競争鎖,

    競争時是非公平的 (仍然是搶占式)

  • 圖中 WaitSet 中的Thread-0,Thread-1 是之前獲得過鎖,但條件不滿足進入 WAITING 狀态的線程,後面講wait-notify 時會分析

注意:
  • synchronized 必須是進入同一個鎖對象的monitor 才有上述的效果; —> 也就要使用同一把鎖
  • 不加 synchronized的鎖對象不會關聯螢幕,不遵從以上規則
    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
它加鎖就是依賴底層作業系統的

mutex

相關指令實作, 是以會造成

使用者态和核心态之間的切換

, 非常耗性能 !
  • 在JDK6的時候, 對synchronized進行了優化, 引入了

    輕量級鎖, 偏向鎖

    , 它們是在JVM的層面上進行加鎖邏輯, 就沒有了切換的消耗~

3、synchronized原理

代碼如下

static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
    synchronized (lock) {
        counter++;
    }
}
           
  • 反編譯後的部分位元組碼
    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
注意:方法級别的 synchronized 不會在位元組碼指令中有所展現

4、synchronized 原理進階

  • 小故事
Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念

5、輕量級鎖 (用于優化Monitor這類的重量級鎖)

通過

鎖記錄

的方式, 場景 : 多個線程交替進入臨界區
  • 輕量級鎖的使用場景

    : 如果一個對象雖然有多個線程要對它進行加鎖,但是加鎖的時間是錯開的(也就是沒有人可以競争的),那麼可以使用

    輕量級鎖來進行優化

  • 輕量級鎖對使用者是透明的,即文法仍然是

    synchronized

    (jdk6對synchronized的優化),假設有兩個方法同步塊,利用同一個對象加鎖
  • eg:

    線程A來操作臨界區的資源,

    給資源加鎖,到執行完臨界區代碼,釋放鎖

    的過程, 沒有線程來競争, 此時就可以使用

    輕量級鎖

    ; 如果

    這期間

    有線程來競争的話, 就會

    更新為重量級鎖(synchronized)

static final Object obj = new Object();
public static void method1() {
     synchronized( obj ) {
         // 同步塊 A
         method2();
     }
}
public static void method2() {
     synchronized( obj ) {
         // 同步塊 B
     }
}
           
  • 每次指向到

    synchronized代碼塊

    時,都會在

    棧幀中

    建立

    鎖記錄(Lock Record)對象

    每個線程都會包括一個鎖記錄的結構

    ,鎖記錄内部可以儲存

    對象的MarkWord

    鎖對象引用reference

    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 鎖記錄

    中的

    Object reference指向鎖對象位址

    ,并且嘗試用

    CAS(compare and sweep)

    棧幀中的鎖記錄的(lock record 位址 00)

    替換

    Object對象的Mark Word

    ,将Mark Word 的值(01)存入鎖記錄(lock record位址)中 ------

    互相替換

    • 01 表示

      無鎖

      (看Mark Word結構, 數字的含義)

    • 00表示

      輕量級鎖

      Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念

重點:

  • 如果

    cas替換成功

    , 獲得了輕量級鎖,那麼

    對象

    對象頭儲存的就是鎖記錄的位址和狀态00

    ,如下所示
    • 線程中鎖記錄, 記錄了鎖對象的鎖狀态标志; 鎖對象的對象頭中存儲了鎖記錄的位址和狀态, 标志哪個線程獲得了鎖
    • 此時

      棧幀

      中存儲了

      對象的對象頭

      中的

      鎖狀态标志

      ,年齡計數器,哈希值等;

      對象的對象頭中

      就存儲了

      棧幀中鎖記錄的位址和狀态00

      , 這樣的話

      對象

      就知道了是

      哪個線程鎖住自己

      Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 如果

    cas替換失敗,有兩種情況

    : ① 鎖膨脹 ② 重入鎖失敗
    • 1、如果是

      其它線程

      已經持有了

      該Object的輕量級鎖

      ,那麼表示有競争,将進入

      鎖膨脹階段

      • 此時

        對象Object

        對象頭中已經存儲了别的線程的

        鎖記錄位址 00

        ,指向了其他線程;
    • 2、如果是

      自己的線程已經執行了synchronized進行加鎖

      ,那麼

      再添加一條 Lock Record 作為重入鎖的計數

      – 線程多次加鎖, 鎖重入
      • 在上面代碼中,

        臨界區中

        又調用了

        method2

        , method2中又進行了一次

        synchronized加鎖操作

        , 此時就會在

        虛拟機棧

        中再開辟一個method2方法對應的棧幀(棧頂), 該棧幀中又會存在一個

        獨立

        Lock Record

        , 此時它發現

        對象的對象頭中指向的就是自己線程中棧幀的鎖記錄

        ; 加鎖也就失敗了. 這種現象就叫做

        鎖重入

        ; 線程中有多少個鎖記錄, 就能表明該線程對這個對象加了幾次鎖 (鎖重入計數)
        Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念

輕量級鎖解鎖流程 :

  • 線程退出synchronized代碼塊

    的時候,如果擷取的是

    取值為 null 的鎖記錄

    ,表示有

    鎖重入

    ,這時重置鎖記錄,

    表示重入計數減一

    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 當線程退出synchronized代碼塊的時候,如果

    擷取的鎖記錄取值不為 null

    ,那麼使用cas将Mark Word的值恢複給對象, 将直接替換的内容還原。
    • 成功則解鎖成功 (輕量級鎖解鎖成功)
    • 失敗,表示有競争, 則

      說明輕量級鎖進行了鎖膨脹

      已經更新為重量級鎖

      ,進入重量級鎖解鎖流程 (Monitor流程)

6、鎖膨脹

  • 如果在嘗試

    加輕量級鎖

    的過程中,

    cas替換操作無法成功

    ,這是有一種情況就是其它線程已經為這個對象加上了輕量級鎖,這是就要進行

    鎖膨脹(有競争)

    将輕量級鎖變成重量級鎖。

  • 當 Thread-1 進行輕量級加鎖時,Thread-0 已經對該對象加了輕量級鎖, 此時發生

    鎖膨脹

    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 這時Thread-1加輕量級鎖失敗,進入鎖膨脹流程
    • 因為

      Thread-1

      線程加輕量級鎖失敗, 輕量級鎖沒有阻塞隊列的概念, 是以此時就要

      為對象申請Monitor鎖(重量級鎖)

      ,讓

      Object指向重量級鎖位址 10

      ,然後

      自己進入Monitor 的EntryList 變成BLOCKED狀态

Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • Thread-0 線程

    執行完

    synchronized同步塊

    時,使用cas将Mark Word的值恢複給對象頭, 肯定恢複失敗,因為對象的對象頭中存儲的是

    重量級鎖的位址,狀态變為10了

    之前的是00, 肯定恢複失敗。那麼會

    進入重量級鎖的解鎖過程

    ,即按照

    Monitor的位址找到Monitor對象,将Owner設定為null,喚醒EntryList中的Thread-1線程

7、自旋鎖優化 (優化重量級鎖競争)

  • 當發生

    重量級鎖競争的時候

    ,還可以使用

    自旋來進行優化 (不加入Monitor的阻塞隊列EntryList中)

    ,如果目前線程自旋成功(即在自旋的時候持鎖的線程釋放了鎖),那麼目前線程就可以不用進行上下文切換

    (持鎖線程執行完synchronized同步塊後,釋放鎖,Owner為空,喚醒阻塞隊列來競争,勝出的線程得到cpu執行權的過程)

    就獲得了

  • 優化的點: 不用将

    線程

    加入到阻塞隊列, 減少cpu切換.
  1. 自旋重試成功的情況

    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  2. 自旋重試失敗的情況

    ,自旋了一定次數還是沒有等到 持鎖的線程釋放鎖, 線程2就會加入Monitor的阻塞隊列(EntryList)
    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 自旋會

    占用 CPU 時間

    ,單核 CPU 自旋就是浪費,多核 CPU 自旋才能發揮優勢。
  • Java 6 之後自旋鎖是自适應

    的,比如對象剛剛的一次自旋操作成功過,那麼認為這次自旋成功的可能性會高,就多自旋幾次;反之,就少自旋甚至不自旋,總之,比較智能。Java 7 之後不能控制是否開啟自旋功能

8、偏向鎖 (biased lock) (用于優化輕量級鎖重入)

場景: 沒有競争的時候, 一個線程中多次使用

synchronized

需要重入加鎖的情況; (隻有一個線程進入臨界區)
  • 在經常需要競争的情況下就不使用偏向鎖, 因為偏向鎖是預設開啟的, 我們可以通過JVM的配置, 将偏向鎖給關閉
  • 将進入臨界區的線程的ID, 直接設定給鎖對象的Mark word, 下次該線程又擷取鎖, 發現線程ID是自己, 就不需要CAS了
  • 輕量級的鎖

    中,我們可以發現,如果同一個線程對同一個對象進行

    重入鎖

    時,也需要執行CAS替換操作,這是有點耗時。
  • 那麼java6開始引入了

    偏向鎖

    ,将進入臨界區的線程的ID, 直接設定給鎖對象的Mark word, 下次該線程又擷取鎖, 發現線程ID是自己, 就不需要CAS了
    • 更新為輕量級鎖的情況 (會進行偏向鎖撤銷) : 擷取偏向鎖的時候, 發現線程ID不是自己的, 此時通過CAS替換操作, 操作成功了, 此時該線程就獲得了鎖對象。(

      此時是交替通路臨界區, 撤銷偏向鎖, 更新為輕量級鎖

      )
    • 更新為重量級鎖的情況 (會進行偏向鎖撤銷) : 擷取偏向鎖的時候, 發現線程ID不是自己的, 此時通過CAS替換操作, 操作失敗了, 此時說明發生了鎖競争。(

      此時是多線程通路臨界區, 撤銷偏向鎖, 更新為重量級鎖

      )

例如:

public class Test {
    static final Object obj = new Object();

    public static void m1() {
        synchronized (obj) {
            // 同步塊A
            m2();
        }
    }

    public static void m2() {
        synchronized (obj) {
            // 同步塊B
            m3();
        }
    }

    public static void m3() {
        synchronized (obj) {
            // 同步塊C
        }
    }
}
           
Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
8.1、偏向鎖狀态 (了解)
  • 運作時中繼資料(Mark Word)

    的結構如下:
    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念

-

Normal:一般狀态,沒有加任何鎖

,前面62位儲存的是對象的資訊,最後2位為狀态(01),倒數第三位表示是否使用偏向鎖(未使用:0)

-

Biased:偏向狀态,使用偏向鎖

,前面54位儲存的目前線程的ID,最後2位為狀态(01),倒數第三位表示是否使用偏向鎖(使用:1)

-

Lightweight:使用輕量級鎖

,前62位儲存的是鎖記錄的指針,最後2位為狀态(00)

-

Heavyweight:使用重量級鎖

,前62位儲存的是Monitor的位址指針,最後2位為狀态(10)

Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 如果開啟了偏向鎖(預設開啟),在建立對象時,對象的Mark Word後三位應該是101
  • 但是偏向鎖預設是有延遲的,不會再程式一啟動就生效,而是會在程式運作一段時間(幾秒之後),才會對建立的對象設定為偏向狀态
  • 如果沒有開啟偏向鎖,對象的Mark Word後三位應該是001

一個對象的建立過程

- 如果開啟了

偏向鎖(預設是開啟的)

,那麼對象剛建立之後,Mark Word 最後三位的值101,并且這是它的

ThreadId

epoch

age(年齡計數器)

都是

,在加鎖的時候進行設定這些的值.

-

偏向鎖預設是延遲

的,不會在程式啟動的時候立刻生效,如果想避免延遲,可以添加虛拟機參數來禁用延遲:

-XX:BiasedLockingStartupDelay=0

來禁用延遲

- 注意 :

處于偏向鎖的對象解鎖後,線程id仍存儲于對象頭中

Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念

輸出結果:

Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 測試

    禁用偏向鎖

    :如果

    沒有開啟偏向鎖

    ,那麼對象建立後最後三位的值為

    001

    ,這時候它的hashcode,age都為0,hashcode是第一次用到

    hashcode

    時才指派的。在上面測試代碼運作時在添加 VM 參數

    -XX:-UseBiasedLocking

    禁用偏向鎖

    (禁用偏向鎖則優先使用輕量級鎖)

    ,退出

    synchronized

    狀态變回 001
    • 禁止偏向鎖, 虛拟機參數

      -XX:-UseBiasedLocking

      ; 優先使用

      輕量級鎖

    • 輸出結果: 最開始狀态為001,然後加輕量級鎖變成00,最後恢複成001
Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
8.2、撤銷偏向鎖-hashcode方法 (了解)
Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 測試

    hashCode

    :當

    調用對象的hashcode方法

    的時候就會

    撤銷這個對象的偏向鎖

    ,因為使用偏向鎖時沒有位置存

    hashcode

    的值了
8.3、撤銷偏向鎖-發生鎖競争 (更新為重量級鎖)
小故事: 線程A門上刻了名字, 但此時線程B也要來使用房間了, 是以要将偏向鎖更新為輕量級鎖. (線程B要線上程A使用完房間之後

(執行完synchronized代碼塊)

,再來使用; 否則就成了競争擷取鎖對象, 此時就要更新為

重量級鎖

了)
偏向鎖、輕量級鎖的使用條件, 都是在于多個線程沒有對同一個對象進行

鎖競争

的前提下, 如果有

鎖競争

,此時就使用重量級鎖。
  • 這裡我們示範的是

    偏向鎖

    撤銷, 變成

    輕量級鎖

    的過程,那麼就得滿足輕量級鎖的使用條件,就是沒有線程對同一個對象進行

    鎖競争

    ,我們使用

    wait

    notify

    來輔助實作
  • 虛拟機參數

    -XX:BiasedLockingStartupDelay=0

    確定我們的程式最開始使用了

    偏向鎖

Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
  • 輸出結果,最開始使用的是

    偏向鎖

    ,但是第二個線程嘗試擷取對象鎖時(前提是: 線程一已經釋放掉鎖了,也就是執行完synchroized代碼塊),發現本來對象

    偏向的是線程一

    ,那麼偏向鎖就會失效,加的就是

    輕量級鎖

    Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念
8.4、撤銷偏向鎖 - 調用 wait/notify (隻有重量級鎖才支援這兩個方法)
(調用wait方法會導緻鎖膨脹而使用重量級鎖)
  • 會使對象鎖變成重量級鎖,因為

    wait/notify方法之後重量級鎖才支援

9、批量重偏向

  • 如果

    對象被多個線程通路,但是沒有競争 (上面撤銷偏向鎖就是這種情況: 一個線程執行完, 另一個線程再來執行, 沒有競争)

    , 這時

    偏向T1的對象仍有機會重新偏向T2

    • 重偏向會重置Thread ID
  • 撤銷偏向鎖101 更新為 輕量級鎖00

    超過

    20次後(超過門檻值)

    ,JVM會覺得是不是偏向錯了,這時會在

    給對象加鎖時,重新偏向至加鎖線程 (T2)。

9.1、批量撤銷偏向鎖
  • 當 撤銷偏向鎖的門檻值超過40以後 ,就會将整個類的對象都改為不可偏向的
9.2、同步省略 (鎖消除)
同步省略
  1. 線程同步的代價是相當高的,同步的後果是

    降低并發性和性能

  2. 在動态編譯同步塊的時候,

    JIT編譯器

    可以

    借助逃逸分析

    來判斷同步塊所使用的鎖對象是否隻能夠被一個線程通路而沒有被釋出到其他線程。
  3. 如果沒有,

    那麼JIT編譯器在編譯這個同步塊的時候就會取消對這部分代碼的同步。這樣就能大大提高并發性和性能。

    這個取消同步的過程就叫同步省略,也叫鎖消除。
  • 例如下面的智障代碼,

    根本起不到鎖的作用

public void f() {
    Object hellis = new Object();
    synchronized(hellis) {
        System.out.println(hellis);
    }
}
           
  • 代碼中對hellis這個對象加鎖,但是hellis對象的生命周期隻在f()方法中,并不會被其他線程所通路到,是以在JIT編譯階段就會被優化掉,優化成:
public void f() {
    Object hellis = new Object();
	System.out.println(hellis);
}
           

位元組碼分析

  • 代碼
public void f() {
    Object hellis = new Object();
    synchronized(hellis) {
        System.out.println(hellis);
    }
}
           
  • 注意:位元組碼檔案中并沒有進行優化,可以看到加鎖和釋放鎖的操作依然存在,

    同步省略操作是在解釋運作時發生的

Java并發程式設計(三) : synchronized底層原理、優化Monitor重量級鎖、輕量級鎖、自旋鎖(優化重量級鎖競争)、偏向鎖一、 Monitor 概念

繼續閱讀