synchronized,是解決併發狀況下數據同步訪問問題的一把利刃。那麼synchronized的底層原理是什麼呢?下面咱們來一層一層剝開它的心,就像剝洋蔥同樣,看個究竟。html
synchronized關鍵字能夠做用於方法或者代碼塊,最主要有如下幾種使用方式,如圖: java
接下來,咱們先剝開synchronized的第一層,反編譯其做用的代碼塊以及方法。bash
public class SynchronizedTest {
public void doSth(){
synchronized (SynchronizedTest.class){
System.out.println("test Synchronized" );
}
}
}
複製代碼
反編譯,可得:數據結構
由圖可得,添加了synchronized關鍵字的代碼塊,多了兩個指令monitorenter、monitorexit。即JVM使用monitorenter和monitorexit兩個指令實現同步,monitorenter、monitorexit又是怎樣保證同步的呢?咱們等下剝第二層繼續探索。多線程
public synchronized void doSth(){
System.out.println("test Synchronized method" );
}
複製代碼
反編譯,可得: 併發
由圖可得,添加了synchronized關鍵字的方法,多了ACC_SYNCHRONIZED標記。即JVM經過在方法訪問標識符(flags)中加入ACC_SYNCHRONIZED來實現同步功能。oracle
剝完第一層,反編譯synchronized的方法以及代碼塊,咱們已經知道synchronized是經過monitorenter、monitorexit、ACC_SYNCHRONIZED實現同步的,它們三做用都是啥呢?咱們接着剝第二層:jvm
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:佈局
If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
谷歌翻譯一下,以下:
每一個對象都與一個monitor 相關聯。當且僅當擁有全部者時(被擁有),monitor纔會被鎖定。執行到monitorenter指令的線程,會嘗試去得到對應的monitor,以下:
每一個對象維護着一個記錄着被鎖次數的計數器, 對象未被鎖定時,該計數器爲0。線程進入monitor(執行monitorenter指令)時,會把計數器設置爲1.
當同一個線程再次得到該對象的鎖的時候,計數器再次自增.
當其餘線程想得到該monitor的時候,就會阻塞,直到計數器爲0才能成功。
能夠看一下如下的圖,便於理解用:
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
谷歌翻譯一下,以下:
monitor的擁有者線程才能執行 monitorexit指令。
線程執行monitorexit指令,就會讓monitor的計數器減一。若是計數器爲0,代表該線程再也不擁有monitor。其餘線程就容許嘗試去得到該monitor了。
能夠看一下如下的圖,便於理解用:
Method-level synchronization is performed implicitly, as part of method invocation and return. A synchronized method is distinguished in the run-time constant pool’s method_info structure by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the executing thread enters a monitor, invokes the method itself, and exits the monitor whether the method invocation completes normally or abruptly. During the time the executing thread owns the monitor, no other thread may enter it. If an exception is thrown during invocation of the synchronized method and the synchronized method does not handle the exception, the monitor for the method is automatically exited before the exception is rethrown out of the synchronized method.
谷歌翻譯一下,以下:
方法級別的同步是隱式的,做爲方法調用的一部分。同步方法的常量池中會有一個ACC_SYNCHRONIZED標誌。
當調用一個設置了ACC_SYNCHRONIZED標誌的方法,執行線程須要先得到monitor鎖,而後開始執行方法,方法執行以後再釋放monitor鎖,當方法不論是正常return仍是拋出異常都會釋放對應的monitor鎖。
在這期間,若是其餘線程來請求執行方法,會由於沒法得到監視器鎖而被阻斷住。
若是在方法執行過程當中,發生了異常,而且方法內部並無處理該異常,那麼在異常被拋到方法外面以前監視器鎖會被自動釋放。
能夠看一下這個流程圖:
好的,剝到這裏,咱們還有一些不清楚的地方,monitor是什麼呢,爲何它能夠實現同步呢?對象又是怎樣跟monitor關聯的呢?客觀別急,咱們繼續剝下一層,請往下看。
montor究竟是什麼呢?咱們接下來剝開Synchronized的第三層,monitor是什麼? 它能夠理解爲一種同步工具,或者說是同步機制,它一般被描述成一個對象。操做系統的管程是概念原理,ObjectMonitor是它的原理實現。
在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 ;
}
複製代碼
ObjectMonitor中幾個關鍵字段的含義如圖所示:
Java Monitor 的工做機理如圖所示:
爲了形象生動一點,舉個例子:
synchronized(this){ //進入_EntryList隊列
doSth();
this.wait(); //進入_WaitSet隊列
}
複製代碼
OK,咱們又剝開一層,知道了monitor是什麼了,那麼對象又是怎樣跟monitor關聯呢?各位帥哥美女們,咱們接着往下看,去剝下一層。
對象是如何跟monitor關聯的呢?直接先看圖:
看完上圖,其實對象跟monitor怎樣關聯,咱們已經有個大概認識了,接下來咱們分對象內存佈局,對象頭,MarkWord一層層繼續往下探討。
在HotSpot虛擬機中,對象在內存中存儲的佈局能夠分爲3塊區域:對象頭(Header),實例數據(Instance Data)和對象填充(Padding)。
對象頭主要包括兩部分數據:Mark Word(標記字段)、Class Pointer(類型指針)。
Mark Word 用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程 ID、偏向時間戳等。
在32位的HotSpot虛擬機中,若是對象處於未被鎖定的狀態下,那麼Mark Word的32bit空間裏的25位用於存儲對象哈希碼,4bit用於存儲對象分代年齡,2bit用於存儲鎖標誌位,1bit固定爲0,表示非偏向鎖。其餘狀態以下圖所示:
對象與monitor怎麼關聯?
事實上,只有在JDK1.6以前,synchronized的實現纔會直接調用ObjectMonitor的enter和exit,這種鎖被稱之爲重量級鎖。一個重量級鎖,爲啥還要常用它呢? 從JDK6開始,HotSpot虛擬機開發團隊對Java中的鎖進行優化,如增長了適應性自旋、鎖消除、鎖粗化、輕量級鎖和偏向鎖等優化策略。
何爲自旋鎖?
自旋鎖是指當一個線程嘗試獲取某個鎖時,若是該鎖已被其餘線程佔用,就一直循環檢測鎖是否被釋放,而不是進入線程掛起或睡眠狀態。
爲什麼須要自旋鎖?
線程的阻塞和喚醒須要CPU從用戶態轉爲核心態,頻繁的阻塞和喚醒顯然對CPU來講苦不吭言。其實不少時候,鎖狀態只持續很短一段時間,爲了這段短暫的光陰,頻繁去阻塞和喚醒線程確定不值得。所以自旋鎖應運而生。
自旋鎖應用場景
自旋鎖適用於鎖保護的臨界區很小的狀況,臨界區很小的話,鎖佔用的時間就很短。
自旋鎖一些思考
在這裏,我想談談,爲何ConcurrentHashMap放棄分段鎖,而使用CAS自旋方式,其實也是這個道理。
何爲鎖消除?
鎖削除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,可是被檢測到不可能存在共享數據競爭的鎖進行削除。
鎖消除一些思考
在這裏,我想引伸到平常代碼開發中,有一些開發者,在沒併發狀況下,也使用加鎖。如沒併發可能,直接上來就ConcurrentHashMap。
何爲鎖租化?
鎖粗話概念比較好理解,就是將多個連續的加鎖、解鎖操做鏈接在一塊兒,擴展成一個範圍更大的鎖。
爲什麼須要鎖租化?
在使用同步鎖的時候,須要讓同步塊的做用範圍儘量小—僅在共享數據的實際做用域中才進行同步,這樣作的目的是 爲了使須要同步的操做數量儘量縮小,若是存在鎖競爭,那麼等待鎖的線程也能儘快拿到鎖。可是若是一系列的連續加鎖解鎖操做,可能會致使沒必要要的性能損耗,因此引入鎖粗話的概念。
鎖租化比喻思考
舉個例子,買門票進動物園。老師帶一羣小朋友去參觀,驗票員若是知道他們是個集體,就能夠把他們當作一個總體(鎖租化),一次性驗票過,而不須要一個個找他們驗票。
咱們直接以一張Synchronized洋蔥圖做爲總結吧,若是你願意一層一層剝開個人心。
歡迎你們關注,你們一塊兒學習,一塊兒討論哈。