Synchronized 鎖機制的實現原理
Synchronized是Java種用於進行同步的關鍵字,synchronized的底層使用的是鎖機制實現的同步。在Java中的每一個對象都可以作為鎖。
Java中synchronized的兩個特性:
-
互斥性:即在同一時間內只允許同一個縣城持有某一個對象鎖,通過這種特性來實現多個線程中的協調機制,這樣在同一時間內只有一個線程對同步的代碼進行訪問,互斥性往往也被稱為原子性。
-
可見性:必須確保在獲取鎖的時候,線程內共享變量的值和主存一致,並且也必須保證在鎖在被釋放前,對共享變量所做的修改,對於隨后獲取鎖的另一個線程是可見的(即在獲取鎖時應該獲得的是最新的共享變量的值),否則另一個線程可能在本地緩存的某一個副本上繼續操作從而導致結果不一致。
synchronized鎖具體的三種形式:
- 對於普通同步方法,鎖對象是當前實例對象,進入同步代碼塊前需要獲得當前對象的鎖。
- 對於靜態同步方法,鎖的是當前類的對象,在Java中每一個類都有一個Class對象。
- 對於同步方法塊,鎖的是synchronized圓括號內的對象,這里的對象可以是一個普通的對象,也可以是一個Class對象,如果是Class對象的話,也就是所謂的類鎖了,而類鎖是通過類的Class對象實現的。
synchronized 的原理
public class SynchronizedTest { public void readFile() throws IOException { synchronized(this) { System.out.println("同步代碼塊"); } } }
經過Javap 反編譯后,
C:\data>javap -c SynchronizedTest.class Compiled from "SynchronizedTest.java" public class test.test.a.SynchronizedTest { public test.test.a.SynchronizedTest(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public void readFile() throws java.io.IOException; Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: getstatic #18 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #24 // String 同步代碼塊 9: invokevirtual #26 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_1 13: monitorexit 14: goto 20 17: aload_1 18: monitorexit 19: athrow 20: return Exception table: from to target type 4 14 17 any 17 19 17 any }
可以看出synchronized 同步語句塊的實現使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結束位置。
下面的是同步方法:
public class SynchronizedTest { public synchronized void readFile() throws IOException { System.out.println("同步代碼塊"); } }
反編譯后如下圖所示:

對於不同的對象頭它們的總結如下表:
長度 | 內容 | 說明 |
---|---|---|
32/64bit | MarkWord | 存儲對象的hashCode,GC分代和鎖信息 |
32/64bit | Klass Point | 存儲到類元數據的指針 |
32/64bit | Array Length | 這個只針對數組對象而言,存儲數組的長度 |
Java對象頭中的MarkWord里面的存儲對象如下表:
鎖狀態 | 25bit | 4bit | 1bit是否偏向鎖 | 2bit鎖標志 |
---|---|---|---|---|
無鎖狀態 | 對象的hashCode | 對象分代年齡 | 0 | 01 |
在運行期間,Mark Word里存儲的數據會隨着鎖標志的變化而變化,它的變化如下表所示:

在64位的虛擬下,Mark Word是64bit的存儲結構,其存儲結構如下表:
鎖狀態 | 25bit | 31bit | 1bit | 4bit | 1bit | 2bit |
---|---|---|---|---|---|---|
cms_free | 分代年齡 | 偏向鎖 | 鎖標志位 | |||
無鎖 | unused | hashCode | 0 | 01 | ||
偏向鎖 | ThreadId(54bit) | Epoch(2bit) | 1 | 01 |
對象頭的最后兩位存儲了鎖的標志,01是初始狀態表示無鎖,其對象頭里存儲的是對象的哈希嗎,隨着鎖級別的不同,對象頭中存儲的內容也會不同。偏向鎖存儲的當前占用此對象的線程ID;而輕量級鎖則是存儲指向線程棧中鎖記錄的指針。
Monitor監視器鎖
其中輕量級鎖和偏向鎖是Java6對synchronized鎖進行優化后增加的,我們稍后會進行分析。這里我們主要分析重量級鎖,也就是通常所說的synchronized對象鎖,鎖標識為10,其中指針指向monitor對象(也稱之為管程或者監視器鎖)的起始地址。每個對象都存在一個monitor與之關聯,對象與其monitor之間也存在着多種實現方式,如monitor可以與對象一起創建或者銷毀或當前線程試圖獲取鎖時自動生成,但一個monitor被某線程持有后,它便處於鎖定狀態。在Java虛擬機(HotSpot)中,monitor是有ObjectMonitor實現的,其主要數據結構如下(位於HotSpot虛擬機源碼ObjectMonitor.hpp文件,C++實現的)
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 ; }
ObjectMonitor 中有兩個隊列,_WaitSet 和 _EntryList ,用來保存ObjectWaiter 對象列表,每個等待鎖的線程都會被封裝成ObjectWaiter 對象,_owner 指向持有ObjectMonitor 對象的線程,當多個線程同時訪問同一同步代碼塊或者同步方法時,首先會進入 _EntryList 隊列,當線程獲取到monitor 后進入_Owner 區域並把 monitor中的 _Owner 變量設置為當前線程,同時monitor 中的計數器count 加1,若線程調用wait() 方法,將釋放當前持有的monitor,_owner變量恢復為null,count 自減 1 ,同時該線程進入_WaitSet 集合中等待被喚醒。若當前線程執行完畢也將釋放monitor(鎖)並復位變量的值,以便其他線程進入獲取monitor(鎖)。如下圖所示:

所以,monitor對象存在於每一個java對象的對象頭(存儲指針的指向),synchronized鎖便是通過這種方式獲取的,也是為什么java中任何對象都可以作為鎖的原因,同時也是 notify/notifyAll/wait 方法等存在於頂級對象Object中的原因。
原文鏈接:https://www.jianshu.com/p/3eda3d375e7e