在併發編程中,synchronized一直是一個元老級的角色,能夠保證方法或者代碼塊在運行時,同一時刻只有一個方法能夠進入臨界區,同時它還能夠保證共享變量的內存可見性,不少人稱之爲重量級鎖。可是隨着jdk1.6進行了各類優化以後,有些狀況下它就並不那麼重了。爲了減小得到鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖。編程
Java中每個對象均可以成爲鎖,具體表現爲如下3種。安全
1.對於普通同步方法,鎖是當前實例對象。多線程
2.對於靜態同步方法,鎖是當前類的Class對象。併發
3.對於同步方法塊,鎖是synchronized括號裏配置的對象。性能
當一個線程試圖訪問同步代碼塊時,它首先要獲得鎖,退出和拋出異常時必須釋放鎖。測試
JVM基於進入和退出Monitor對象來實現方法同步和代碼同步,但二者的實現細節不同。優化
monitorenter指令在編譯後插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處,JVM要保證每一個monitorenter必須有monitorexit與之配對。每個對象都有一個monitor與之關聯,而且一個monitor被持有後,它將處於被鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor的全部權,即嘗試得到對象的鎖。線程
JAVA對象頭指針
synchronized用的鎖是存在Java對象頭裏的。Hotspot虛擬機的對象頭主要包含兩部分數據:Mark Word(標記字段)、Klass Pointer(類型指針)。對象
Klass Pointer: 是對象指向類元素的指針,虛擬機經過這個指針來肯定對象是哪一個類的實例
Mark Word:存儲對象運行時候對象自身的數據,hashCode以及GC分代年紀,鎖狀態標識、線程持久的鎖、偏向線程ID
在運行期間,Mark Word裏存儲的數據會隨着鎖標誌位的變化而變化。
Mark Word的狀態變化
偏向鎖:鎖標誌位是01。存儲偏向的線程ID,
輕量級鎖:鎖標誌位00。指向棧中鎖記錄的指針。
重量級鎖: 鎖的標誌位10。指向互斥量(重量級鎖)指針。
鎖的升級與對比
jdk1.6爲了減小得到鎖和釋放鎖帶來的性能消耗,引入了「偏向鎖」和「輕量級鎖「。
鎖一共有四種狀態,級別從低到高依次爲:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態。鎖能夠升級不能夠降級,這種策略是爲了提升得到鎖和釋放鎖的效率。
1.偏向鎖
在大多數狀況下,鎖不只不存在多線程競爭,並且老是由同一個線程屢次得到,爲了讓線程得到鎖的代價更低而引入偏向鎖。當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲偏向鎖的線程ID,之後該線程在進入和退出同步塊時不須要進行CAS操做,只須要簡單的測試下對象頭的Mark Word裏是否存在當前線程的偏向鎖。若是測試成功,表示線程已經已經得到了鎖。若是測試失敗,則須要再測試一下Mark Word中偏向鎖的標識是否設置爲1(表示當前是偏向鎖)。若是沒有設置,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。
偏向鎖的撤銷:
偏向鎖使用了一種等到了競爭出現才釋放的機制,因此當前其餘線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖。偏向鎖的撤銷,須要等待全局安全點。它會首先暫停擁有偏向鎖的線程,而後檢查持有偏向鎖的線程是否存活,若是線程不處於活動狀態,則將對象頭設置成無鎖狀態,若是線程任然活着,擁有偏向鎖的棧會被執行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word要麼從新偏向於其餘線程,要麼恢復到無鎖或者標記對象不適合做爲偏向鎖(將鎖升級爲輕量級鎖)。最後喚醒暫停的線程。
關閉偏向鎖:
偏向鎖在Java6和Java7裏默認是啓用的。
2.輕量級鎖
輕量級鎖加鎖
線程在執行同步塊以前,JVM會先在當前線程的棧幀中建立用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中。而後線程嘗試使用CAS將對象頭中的Mark Down替換成指向鎖記錄的指針。若是成功,當前線程得到鎖,若是失敗,表示其餘線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。
輕量級鎖解鎖
輕量級解鎖時,會使用原子的CAS操做將Displaced Mark Word替換回到對象頭,若是成功,則表示沒有競爭發生。若是失敗,表示當前鎖存在競爭,鎖就會膨化成重量級鎖。
由於自旋會消耗CPU,爲了不無用的自旋(好比得到鎖的線程被阻塞了),一旦鎖升級成爲重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於這種狀態時,其餘競爭線程都會被阻塞住,當持有鎖的線程釋放以後會喚醒(notifyAll)這些線程,被喚醒的線程就會進行新一輪的奪鎖之爭。