前言:
在併發編程中,常常用到synchronized關鍵詞,老是感受使用它會很重。隨着Java SE 1.6對synchronize進行了各類優化,引入了偏向鎖和輕量級鎖,在某些狀況下,減小了得到鎖和釋放鎖帶來得性能消耗。
java中每一個對象均可以做爲一個鎖,具體的表現有如下三種形式:前端
當一個線程試圖訪問同步代碼塊時,必須首先獲取到鎖,退出同步代碼塊時或拋出異常必須釋放鎖。
JVM基於進入與退出Monitor對象實現方法同步與代碼塊同步,不過二者的實現細節不太同樣,可參見以下字節碼所示。java
public class SynchronizedDemo { /** * 同步方法 */ public synchronized void testSynchronizedMethod () { System.out.println("test synchronized method"); } /** * 同步靜態方法 */ public synchronized static void testSynchronizedStaticMethod () { System.out.println("test synchronized static method"); } /** * 方法同步塊 */ public void testSynchronizedMethodBlock() { synchronized (this) { System.out.println("test synchronized method block"); } } }
進入java文件所在目錄,經過命令行進行編譯:javac SynchronizedDemo.java
而後同目錄下經過以下命令,進行查看編譯後字節碼的詳細信息:javap -verbose SynchronizedDemo.class
如圖,任何對象有一個Monitor與之對應,線程執行到monitorenter時會嘗試獲取Monitor對象的全部權,即嘗試獲取對象上的鎖。編程
Monitor做爲操做系統的一種原語,具體由相應的編程語言實現。每一個Monitor對象又包括:數組
- _owner:記錄當前持有的鎖的線程,也能夠了理解成鎖的臨界區
- _entrySet:一個隊列,記錄全部阻塞等待鎖的線程
- _waitSet:一個隊列,記錄全部調用wait未被喚醒的線程
當一個線程訪問Object鎖時,會被放入_entrySet中等待,若是該線程獲取到鎖,成爲當前鎖的_owner;期間,線程邏輯上缺乏外部條件時,線程經過調用wait方法釋放鎖,進入到_waitSet隊列,等到條件知足時,又被喚醒與_entrySet一塊兒競爭_owner;這個外部條件在monitor機制中稱爲條件變量。安全
Java對象包括了對象頭、屬性字段、補齊區域等。
對象頭在最前端,包括了兩部分(非數組類型)或三部分(數組類型,多存在數據的長度),結構以下所示多線程
長度(32位機/64位機 bit) | 內容 | 說明 |
---|---|---|
32/64 | Mark Word | 存儲對象的hashCode和鎖信息等 |
32/64 | Class Metadata Address | 存儲到對象類型數據的指針 |
32/32 | Array Length | 數組的長度(若是對象是數組) |
對象頭的Mark Word會有指向管程Monitor的指針。
補齊區域:因爲JVM要求java的對象佔的內存大小應該是8bit的倍數,因此會有幾個字節用於把對象的大小補齊到8bit的倍數,沒有其它特別功能。
其中Mark Word的存儲數據隨着鎖標誌的變化以下:
併發
java SE 1.6引入偏向鎖與輕量級鎖後,鎖一共有4中狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖和重量級鎖狀態。且鎖會隨着競爭狀況逐步升級,但不可降級(基於JVM的一個假定:「假定一旦破壞了上一級鎖的升級,就認爲該假定之後也不成立」)。編程語言
爲了讓線程獲取鎖的代價更低而引入偏向鎖,由於多線程中,有些狀況下,獲取鎖的線程同時只會有一個。
以下,線程1演示了偏向鎖初始化的流程,線程2協助演示了偏向鎖撤銷的流程。性能
偏向鎖默認是開啓的,可以使用JVM參數關閉:-XX:-UseBiasedLocking,那麼程序默認會進入輕量級鎖
引入輕量級鎖,爲了避免申請互斥量,包括系統調用引發的內核態與用戶態的切換、線程阻塞形成的線程切換等。優化
在線程中,虛擬機會在當前線程的棧幀中創建一個名爲鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝,官方稱Displaced Mark Word。
以下,線程1與線程2演示了輕量級鎖膨脹爲重量級鎖的流程。
內置鎖在java中被抽象爲監視器鎖(monitor),對於重量級鎖,監視器鎖直接對應底層操做系統中的互斥量(mutex),這種同步成本很是高,包括系統調用引發的內核態與用戶態切換、線程阻塞形成的線程切換等。
關於不一樣鎖的優缺點對比,以下所示
鎖 | 有點 | 缺點 | 使用場景 |
---|---|---|---|
偏向鎖 | 加鎖和解鎖不須要額外的消耗,和執行非同步方法時相比僅存在納秒級的差距;畢竟僅第一執行CAS操做 | 若是線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗 | 適用於只有一個線程訪問同步的場景 |
輕量級鎖 | 競爭的線程不會阻塞,提升了程序的響應速度;相比偏向鎖,獲取和釋放鎖均執行一次CAS操做 | 若是使用得不到鎖競爭的線程,會使用自旋會消耗CPU資源 | 追求響應時間,同步塊執行速度很是快 |
重量級鎖 | 線程競爭不使用自旋,不會消耗CPU | 線程阻塞,響應時間緩慢 | 追求吞吐量,同步塊執行速度較長 |