synchronized 關鍵字解析


在jdk1.6之前,synchronized是重量級鎖,在jdk1.6級以後jvm對鎖的實現進行了優化,如使用偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。
鎖主要存在四中狀態,依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,他們會隨着競爭的激烈而逐漸升級。

1、Syncronized應用

在java代碼中synchronized關鍵字可以使用在代碼塊和方法中,根據Synchronized用的位置可以有以下使用場景:

分類 具體分類 被鎖對象 僞代碼
方法 靜態方法 類對象 //靜態方法,鎖住的是類對象
public synchronized static void method(){}
實例方法 類的實例對象 //實例方法,鎖住的是該類的實例對象
public synchronized void method(){
}
代碼塊 實例對象 類的實例對象 //同步代碼塊,鎖住的是該類的實例對象
synchronized (this){ }
class對象 類對象 //同步代碼塊,鎖住的是該類的類對象
synchronized(Test.class){}
任意實例對象Object 實例對象Object Client client=new Client();
//同步代碼塊,鎖住的是配置的實例對象,鎖對象爲client
synchronized(client){}

由表可知,synchronized既可以修飾方法也可以修飾代碼塊,具體詳見上表。這裏的需要注意的是:如果鎖的是類對象的話,儘管new多個實例對象,但他們仍然是屬於同一個類依然會被鎖住,即線程之間保證同步關係

2、JAVA對象頭

說到鎖,不得不說到java的對象頭,對象頭中包含了一些關於鎖的字段。瞭解對象頭對於學習synchronized鎖有極大的幫助。
在這裏插入圖片描述
由圖可以知道,在64位的hotspot虛擬機下,java的對象頭包括兩部分信息,第一部分是Mark Word,主要存儲對象自身的運行時數據,如哈希碼,GC分代年齡,鎖狀態標誌,偏向線程ID等內容。對象頭的另外一部分就是類型指針(klass Word),虛擬機通過這個指針確定該對象是哪個類的實例。

在這裏我們主要研究Mark Word,由圖可以知道,給對象上鎖,其實就是改變對象頭的狀態,各個鎖的狀態如圖所示。
在這裏插入圖片描述

3、Synchronized的原理

3.1、什麼是monitor

我們可以把它理解爲一個同步工具,也可以描述爲一種同步機制。每個對象都存在着一個 monitor 與之關聯,對象與其 monitor 之間的關係有存在多種實現方式,如monitor可以與對象一起創建銷燬或當線程試圖獲取對象鎖時自動生成,但當一個 monitor 被某個線程持有後,它便處於鎖定狀態。在Java虛擬機(HotSpot)中,monitor是由ObjectMonitor實現的,其主要數據結構如下:

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 ;
 }

_owner:指向持有ObjectMonitor對象的線程
_WaitSet:存放處於wait狀態的線程隊列
_EntryList:存放處於等待鎖block狀態的線程隊列
_count:用來記錄該線程獲取鎖的次數

也就是說ObjectMonitor中有兩個隊列,_WaitSet 和 _EntryList,用來保存ObjectWaiter對象列表( 每個等待鎖的線程都會被封裝成ObjectWaiter對象),_owner指向持有ObjectMonitor對象的線程,當有多個線程訪問同一塊同步代碼塊的時候,線程會線程會進入_EntryList,當線程獲取到對象的monitor 後進入 _Owner 區域並把monitor中的owner變量設置爲當前線程,同時monitor中的計數器count加1,若線程調用 wait() 方法,將釋放當前持有的monitor,owner變量恢復爲null,count自減1,同時該線程進入 WaitSet集合中等待被喚醒。若當前線程執行完畢也將釋放monitor(鎖)並復位變量的值,以便其他線程進入獲取monitor。

3.2、Synchronized修飾代碼塊

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}

編譯上述代碼並使用javap反編譯後得到字節碼如下:
在這裏插入圖片描述
monitorenter:每個對象都是一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:

  • 如果monitor的進入數爲0,則該線程進入monitor,然後將進入數設置爲1,該線程即爲monitor的所有者;
  • 如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數加1;
  • 如果其他線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再重新嘗試獲取monitor的所有權;

monitorexit:執行monitorexit的線程必須是monitor所對應持有者。指令執行時,monitor的進入數減1,如果減1後進入數爲0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個 monitor 的所有權。

需要說明的是:monitorexit指令出現了兩次,第1次爲同步正常退出釋放鎖;第2次爲發生異步退出釋放鎖。

通過上面的描述,我們應該能很清楚的看出Synchronized的實現原理,Synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,這就是爲什麼只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因

3.3、Synchronized修飾實例方法

public class SynchronizedMethod {
    public synchronized void method() {
        System.out.println("Hello World!");
    }
}

查看反編譯後結果:
在這裏插入圖片描述
從編譯的結果來看,方法的同步並沒有通過指令 monitorenter 和 monitorexit 來完成,是隱式同步,JVM可以從方法常量池中的方法表結構(method_info Structure) 中的 ACC_SYNCHRONIZED 訪問標誌區分一個方法是否同步方法。當方法調用時,調用指令將會 檢查方法的 ACC_SYNCHRONIZED訪問標誌是否被設置,如果設置了,執行線程將先持有monitor,然後再執行方法,最後再方法完成(無論是正常完成還是非正常完成)時釋放monitor。在方法執行期間,執行線程持有了monitor,其他任何線程都無法再獲得同一個monitor。如果一個同步方法執行期間拋出了異常,並且在方法內部無法處理此異常,那這個同步方法所持有的monitor將在異常拋到同步方法之外時自動釋放。

4、線程中的start方法時如何調用run方法的

我們知道,synchronized同步鎖,是離不開線程的,那麼線程時如何執行的呢?
首先我們啓動一個線程需要調用start方法,進入start方法,可以知道start方法會調用本地方法start0,編譯openJDK源碼可以知道,這個start0會執行Thread.c文件請求hotspot創建javaThread方法,最終調用操作系統函數pthread_create方法,pthread_create方法需要傳入四個參數,第三個參數就是線程的主體方法(參數爲java_start),在這個主體函數裏面 通過某種方式調用java的run方法。

也就是說Java執行線程,最終調用操作系統函數。Java創建一個線程等於操作系統創建一個線程。

5、notify和notifyAll的區別

當一個線程進入wait之後,就必須等待其他線程notify/notifyAll,使用notifyAll可以喚醒所有處於wait狀態的線程,使其重新進入鎖的競爭隊列中,使用notify只能喚醒一個。如果沒把握,建議 notifyAll,防止 notify因爲信號丟失而造成程序異常。

6、偏向鎖

在學習偏向鎖之前,我們先了解一下一些關於偏向鎖的JVM參數設置信息。通過-XX:+PrintFlagsFinal 查看jvm默認參數,其中有這麼一段:
在這裏插入圖片描述
BiasedLockingStartupDelay=4000:這是偏向鎖啓動延遲的參數,默認的情況下爲延遲4s。

BiasedLockingBulkRdbiasThreshold=20: 這是偏向鎖批量重偏向的閾值,在後面的章節中會進行說明。

BiasedLockingBulkRevokeThreshold=40:這是偏向鎖批量撤銷的閾值,在後面的章節中會進行說明。

我們可以通過 -XX:BiasedLockingStartupDelay=0 來關閉偏向鎖延遲。通過 -XX:-UseBiasedLocking=false 關閉偏向鎖,程序默認會進入輕量級鎖狀態。

那麼爲什麼JVM虛擬機在啓動的時候會會將偏向鎖默認延遲4s呢?
因爲程序執行的時候會先啓動jvm、gc等線程,多個線程搶佔資源,所以存在資源競爭,所以如果不取消偏向鎖,就會不斷的進行鎖升級,消除偏向鎖,這些過程會浪費資源,所以直接取消偏向鎖,4s以後開啓偏向鎖。

偏向鎖的引入

HotSpot的作者經過研究發現,大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,爲了讓線程獲得鎖的代價更低而引入了偏向鎖。

通過JOL獲取偏向鎖對象頭信息,添加 -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 來禁用偏向鎖延遲:

public class A {
    boolean flag=false;
}
public class JOLExample2 {
   static A a;
    public static void main(String[] args) {
       a=new A();
        out.println("befor lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        synchronized (a){
            out.println("lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

運行結果爲:
在這裏插入圖片描述
我們的計算機中存儲數據是以小端的方式,所謂小端模式(Little-endian), 是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內在的低地址中,這種存儲模式將地址的高低和數據位 權有效結合起來,高地址部分權值高,低地址部分權值低,和我們的邏輯方法一致;

因此上圖中畫紅色框的地方就是Mark Word中後8位,這8位的後三位便是偏向鎖標識(1bit)和鎖的標誌位(2bit)

before lock 、lock ing 和after lock的結果都爲00000101,但是before lock 下沒有線程持有鎖,只有一個偏向狀態。後面兩個都有線程持有鎖(都有線程id,並且相同)。也就是說在偏向鎖釋放鎖之後,在沒有其他線程獲取鎖的情況下,依然持有這個偏向鎖。

偏向鎖的獲取

當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲鎖偏向的線程ID,以後該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word裏是否存儲着指向當前線程的偏向鎖。如果測試成功,表示線程已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

偏向鎖的撤銷

加鎖和對象的hashCode的關係

我們知道,在有鎖狀態下,Mark Word的前56bit位置被鎖指針或者線程id佔用,對象的hashCode存到哪裏呢,還是不存在hashCode了呢?
先看一段代碼,在加鎖之前計算對象hashCode值:

public class JOLExample8 {
   static A a;
    public static void main(String[] args) throws Exception {
        //保證偏向鎖已經被開啓
        Thread.sleep(5000);
        a= new A();
        a.hashCode();
        out.println("befor lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        synchronized (a){
            out.println("lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

運行結果:

befor lock
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 9d e1 40 (00000001 10011101 11100001 01000000) (1088527617)
      4     4        (object header)                           15 00 00 00 (00010101 00000000 00000000 00000000) (21)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

lock ing
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           40 f2 89 02 (01000000 11110010 10001001 00000010) (42594880)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

after lock
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 9d e1 40 (00000001 10011101 11100001 01000000) (1088527617)
      4     4        (object header)                           15 00 00 00 (00010101 00000000 00000000 00000000) (21)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

before lock:無鎖; lock ing:輕量鎖 after lock:無鎖。說明如果對象已經計算了hashcode就不能偏向了。

下面再看一段代碼,當線程處於偏向鎖狀態,需要計算HashCode的情況:

public class JOLExample8 {
   static A a;
    public static void main(String[] args) throws Exception {
        //保證偏向鎖已經被開啓
        Thread.sleep(5000);
        a= new A();
        out.println("befor lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        synchronized (a){
            out.println("lock ing");
            a.hashCode();
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

運行結果:

befor lock
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

lock ing
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           2a 13 3a 26 (00101010 00010011 00111010 00100110) (641340202)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

after lock
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           2a 13 3a 26 (00101010 00010011 00111010 00100110) (641340202)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

由結果可知,lock ing的時候 鎖狀態爲010 重量鎖。因此當一個對象當前正處於偏向鎖狀態,並且需要計算其hash code的話,則它的偏向鎖會被撤銷,並且鎖會膨脹爲重量鎖。

下面再看一段代碼,當一個對象當前正處於偏向鎖狀態,調用wait方法的情況:

public class JOLExample9 {
    static A a;
    public static void main(String[] args) throws Exception {
        Thread.sleep(5000);
        a = new A();
        out.println("befre lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());

        Thread t1 = new Thread() {
            public void run() {
                synchronized (a) {
                    try {
                        synchronized (a) {
                            System.out.println("before wait");
                            out.println(ClassLayout.parseInstance(a).toPrintable());
                            a.wait();
                            System.out.println(" after wait");
                            out.println(ClassLayout.parseInstance(a).toPrintable());
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        Thread.sleep(7000);
        synchronized (a) {
            a.notifyAll();
        }
    }
}

運行結果:

befre lock
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 f8 (10011111 11000001 00000000 11111000) (-134168161)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

before wait
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 10 5f 29 (00000101 00010000 01011111 00101001) (694095877)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 f8 (10011111 11000001 00000000 11111000) (-134168161)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 after wait
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           9a 33 7d 26 (10011010 00110011 01111101 00100110) (645739418)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 f8 (10011111 11000001 00000000 11111000) (-134168161)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

根據結果可知,調用wait方法後,會變成010重量鎖。

總結:

  • 當一個對象已經計算過hash code,它就無法進入偏向鎖狀態;
  • 當一個對象當前正處於偏向鎖狀態,並且需要計算其 hash code的話,則它的偏向鎖會被撤銷,並且鎖會膨脹爲重量鎖;
  • 當一個對象當前正處於偏向鎖狀態,調用wait方法則立刻變成重量鎖。
  • 重量鎖的實現中,ObjectMonitor類裏有字段可以記錄非加鎖狀態下的mark word,其中可以存儲hash code的值。或者簡單說就是重量鎖可以存下hash code。可以通過這個鏈接查看ObjectMonitor對象源碼:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/8c0fa90986a6/src/share/vm/runtime/objectMonitor.hpp

7、輕量鎖

當多個線程競爭鎖時,偏向鎖就會撤銷,偏向鎖撤銷之後會升級爲輕量級鎖。這時候的多線程競爭鎖是指 多個線程交替執行,多個線程可能不是順序執行,會自旋一個線程上下文的時間,再去獲取鎖,也會是輕量級鎖。
多個線程交替執行有以下幾種方式:
I、第一個線程結束,第二個線程執行 沒有競爭——偏向鎖膨脹爲輕量級鎖
II、第一個線程 同步結束,線程未結束,第二個線程執行 ——沒有競爭——偏向鎖膨脹爲輕量級鎖。

8、重量鎖

重量級鎖通過對象內部的監視器(monitor)實現,其中monitor的本質是依賴於底層操作系統的Mutex Lock實現,操作系統實現線程之間的切換需要從用戶態到內核態的切換,切換成本非常高。

9、偏向鎖、輕量級鎖、重量級鎖性能對比

我們測試一下調用同步方法10億來計算10億的i++,對比偏向鎖和輕量級鎖、重量級鎖的性能。

public class A {
    public synchronized void parse(){
        JOLExample6.countDownLatch.countDown();
    }
}

首先設置關閉偏向鎖延遲的情況下運行線面代碼測試偏向鎖性能。(添加 -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 來禁用偏向鎖延遲)。再開啓偏向鎖延遲運行下面代碼測試輕量鎖性能。

public class JOLExample4 {
    public static void main(String[] args) throws Exception {
        A a = new A();
        long start = System.currentTimeMillis();
        //調用同步方法1000000000L 來計算1000000000L的++,對比偏向鎖和輕量級鎖的性能
        //如果不出意外,結果灰常明顯
        for(int i=0;i<1000000000L;i++){
            a.parse();
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }

測試重量級鎖性能:

public class JOLExample6 {
    static CountDownLatch  countDownLatch = new CountDownLatch(1000000000);
    public static void main(String[] args) throws Exception {
        final A a = new A();
        long start = System.currentTimeMillis();
        //調用同步方法1000000000L 來計算1000000000L的++,對比偏向鎖和輕量級鎖的性能
        //如果不出意外,結果灰常明顯
        for(int i=0;i<2;i++){
            new Thread(){
                @Override
                public void run() {
                    while (countDownLatch.getCount() > 0) {
                        a.parse();
                    }
                }
            }.start();
        }
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
}}	

運行結果如下所示:

偏向鎖 輕量鎖 重量鎖
3553ms 29075ms 61359ms

由表可知,在性能上面,偏向鎖>輕量鎖>重量鎖。

10、鎖的批量重偏向和批量撤銷重偏向

前面提到了JVM關於鎖的幾個默認參數中有BiasedLockingBulkRdbiasThreshold=20: 偏向鎖默認批量重偏向的閾值。以及
BiasedLockingBulkRevokeThreshold=40:偏向鎖默認批量撤銷的閾值。我們可以通過代碼進行驗證。

public class JOLExample12 {
    static List<A> list = new ArrayList<A>();
    static List<A> list2 = new ArrayList<A>();
    public static void main(String[] args) throws Exception {

        Thread t1 = new Thread() {
            public void run() {
                for (int i=0;i<100;i++){
                    A a = new A();
                    synchronized (a){
                        list.add(a);
                        list2.add(a);
                    }
                }

            }

        };
        t1.start();
        t1.join();
        out.println("befre t2");
        out.println("list.get(1) 是偏向鎖:"+ClassLayout.parseInstance(list.get(1)).toPrintable());
        out.println("list.get(50) 是偏向鎖:"+ClassLayout.parseInstance(list.get(50)).toPrintable());
        Thread t2 = new Thread() {
            public void run() {
                for (int i = 0; i < list.size(); i++){
                    A a = list.get(i);
                    synchronized (a){
                        //i=10 未達到批量重偏向的閾值20,還是輕量級鎖
                       if (i==10){
                           out.println("輕量級鎖 t2 ing  ------------"+i );
                           out.println(ClassLayout.parseInstance(a).toPrintable());

                       }
                        //i=19 達到批量重偏向的閾值20,撤銷爲偏向鎖
                       if (i==19){
                           out.println("偏向鎖 t2 ing  ------------"+i );
                           out.println(ClassLayout.parseInstance(a).toPrintable());
                           //到此爲止,累計重偏向20次
                           System.out.println("到此爲止,a對象累計重偏向20次");

                       }

                        //i=24 超過批量重偏向的閾值20,都爲偏向鎖
                        if (i==24){
                            out.println("偏向鎖 t2 ing  ------------"+i );
                            out.println(ClassLayout.parseInstance(a).toPrintable());

                        }
                   }
                }
            }
        };
        t2.start();
         t2.join();

        //添加多餘線程,防止線程ID複用
        new Thread() {
            public void run() {
                System.out.println("tmp---------------------------------------------");
            }
        }.start();

        Thread t3 = new Thread() {
            public void run() {
                for (int i = 20; i < list.size(); i++) {
                    A a=list.get(i);

                    if (i==20){
                        out.println("t3 ing 未加鎖狀態下,預期是t2的偏向鎖  ------------" +i);
                        out.println(ClassLayout.parseInstance(a).toPrintable());

                    }

                    synchronized (a){
                        //i=20 沒有達到批量撤銷偏向鎖的閾值40 是輕量鎖
                        if (i==20){
                            out.println("輕量級鎖 t3 ing  ------------" +i);
                            out.println(ClassLayout.parseInstance(a).toPrintable());

                        }
                        //i=40 加上前面的20次,累計達到批量撤銷偏向鎖的閾值40 撤銷t2偏向鎖 升級爲輕量級鎖
                        if (i==40){
                            out.println("撤銷t2偏向鎖 升級爲輕量級鎖 t3 ing  ------------" +i);
                            out.println(ClassLayout.parseInstance(a).toPrintable());

                        }
                        //i=25 累計達到批量撤銷偏向鎖的閾值40 撤銷t2偏向鎖 升級爲輕量級鎖
                        if (i==50){
                            out.println("輕量級鎖 t3 ing  ------------" +i);
                            out.println(ClassLayout.parseInstance(a).toPrintable());

                        }

                    }
                }

            }
        };
        t3.start();
    }
}

運行結果如下:

befre t2
list.get(1) 是偏向鎖:com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 30 22 28 (00000101 00110000 00100010 00101000) (673329157)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

list.get(50) 是偏向鎖:com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 30 22 28 (00000101 00110000 00100010 00101000) (673329157)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

輕量級鎖 t2 ing  ------------10
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           60 f0 95 29 (01100000 11110000 10010101 00101001) (697692256)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

偏向鎖 t2 ing  ------------19
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 81 10 29 (00000101 10000001 00010000 00101001) (688947461)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

到此爲止,a對象累計重偏向20次
偏向鎖 t2 ing  ------------24
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 81 10 29 (00000101 10000001 00010000 00101001) (688947461)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

tmp---------------------------------------------
t3 ing 未加鎖狀態下,預期是t2的偏向鎖  ------------20
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 81 10 29 (00000101 10000001 00010000 00101001) (688947461)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

輕量級鎖 t3 ing  ------------20
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           20 f2 b5 29 (00100000 11110010 10110101 00101001) (699789856)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

撤銷t2偏向鎖 升級爲輕量級鎖 t3 ing  ------------40
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           20 f2 b5 29 (00100000 11110010 10110101 00101001) (699789856)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

輕量級鎖 t3 ing  ------------50
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           20 f2 b5 29 (00100000 11110010 10110101 00101001) (699789856)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1給對象a加鎖,list中的a對象全部是t1持有的偏向鎖100個。t2執行加鎖的過程中拿到t1持有的偏向鎖,會撤銷t1偏向鎖,升級爲t2輕量級鎖,t2經過20次鎖撤銷,將後面的80個t1持有的偏向鎖全部轉換爲爲t2持有的偏向鎖(20前面的鎖不會變,還是t2持有的輕量鎖,20次以後 就只會進行if判斷,到100次)。

這時候如果t3來加鎖,假如從20開始,將t2持有的偏向鎖進行撤銷,升級爲t3輕量鎖,當達到40的閾值後(也就是類對的對象累計撤銷40次),根據jvm的設置,不會將t3的輕量鎖重偏向爲偏向鎖,而是將40以後的t2的偏向鎖撤銷,升級爲t3持有的輕量鎖。

總結:以class爲單位,爲每個class維護一個偏向鎖撤銷計數器,每一次該class的對象發生偏向鎖撤銷操作時,該計數器+1,當這個值達到重偏向閾值(默認20)時,JVM就會認爲該class 的偏向鎖有問題,因此會進行批量重偏向。每個class 對象會有一個對應的epoch字段,每個處於偏向鎖狀態對象的Mark Word中也有該字段,其初始值設置爲創建該對象時,class中的epoch值,每次發生批量重偏向時,就將該值+1,同時遍歷JVM中所有線程,找到該class所有正處於加鎖狀態的偏向鎖,將其epoch值改爲新值,下次獲取鎖時,當發現當前對象的epoch值和class的epoch值不相等,那就算當前已經偏向了其他線程,也不會進行撤銷操作,而是直接通過CAS操作箱其Mark Word的Thread id改爲當前線程id。

11、鎖的膨脹

鎖的膨脹過程就是 偏向鎖 到輕量鎖 到 重量鎖的過程。下面盜用一張圖來說明具體流程。

在這裏插入圖片描述

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