- 在JDK1.5之前,我們要想實現線程同步,只能通過synchronized關鍵字這一種方式達成。synchronized關鍵字是JVM實現的一種內置鎖。從底層角度來說,這種鎖釋放和獲取都是jVM幫我們隱式實現的。
- 從JDK1.5開始並發包引入了Lock鎖,Lock鎖是基於Java實現的。因此鎖的獲取和釋放都是由java代碼實現的。然而synchronized是基於操作系統底層的Mutex Lock來實現的。每次獲取釋放鎖都會帶來用戶態和內核態的切換,了解操作系統相關的基礎知識都清楚,這種切換是非常消耗cpu性能的,它需要保存和恢復一些狀態數據等,同時也影響到鎖的性能。如下圖所示
- 從JDK1.6開始synchronized鎖的實現發生了很大的變化,jvm引入了相應的優化手段來提升synchronized鎖的性能。這種優化涉及到偏向鎖,輕量級鎖,和重量級鎖,從而減少鎖競爭帶來的用戶態和內核態之間的切換。鎖升級的出發點就是減少戶態和內核態之間的切換。盡量使程序一直出入用戶態。鎖的優化實際上是通過java對象頭上一些標志位來實現的。
對象實例
在JDK1.6開始,對象實例在堆中會被划分三個組成部分:對象頭,實例數據,和對其填充。
- 實例數據:對象的相關屬性
- 對其填充:確保數據長度一致。有些沒有數據的自動填充一些空間。
對象頭
- Mark Word
- 指向類的指針
- 數組長度
我們在鎖升級的過程中只需要關注Mark World(它記錄了對象,鎖和垃圾回收相關的信息,在64位JVM中其長度為64bit)的位信息包括了如下組成信息:
- 無鎖標記: 當前對象沒有上鎖。
1. 偏向鎖標記:
2. 輕量級鎖標記:
3. 重量級鎖標記:直接從用戶態切換到內核態。
4. GC標記: 判斷對象是否可被垃圾收回收掉。
- 對於synchronized鎖來說,鎖的升級主要是通過Mark World的鎖標記位與是否是偏向鎖標志來達成的。synchronized關鍵字所對應的鎖都是從偏向鎖開始的,隨着鎖競爭的不斷升級逐步演化至輕量級鎖,最后則變成重量級鎖。
偏向鎖
-
總體而言如下圖所示:
-
偏向鎖的設置
- 針對一個線程來說,它的主要作用就是優化同一個線程多次獲取一個鎖的情況;如果一個synchronized方法被一個線程訪問,那么這個方法所在的對象就會在其Mark World的將偏向鎖進行標記,同時還會有一個字段來存儲該線程的Id,當這個線程再次訪問同一個synchronized方法時,它會檢測這個對象的Mark World的偏向鎖已經是否指向了該線程Id,如果是的話那么該線程就無需在進入管程(內核態),而直接進入到該方法,這些操作都是在用戶態進行的。
-
偏向鎖的撤銷
- 偏向鎖使用了一種等到競爭才釋放鎖的機制,所以上述的偏向鎖的設置只是針對單個線程而言。 當其他線程線程B嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,偏向鎖的釋放,需要等待全局安全點(這個時間點上沒有正在執行字節碼)。它會首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程釋放存活,如果線程不處於活動狀態,則對象頭設置無鎖狀態,如果線程仍然活着,擁有偏向鎖的棧就會被執行,遍歷偏向鎖對象的鎖記錄,棧中的鎖記錄和對象頭的Mark World要么重新偏向其他線程,要么恢復成無鎖(換句話說就是持有偏向鎖的線程不存活就對象頭設置無鎖狀態,如果線程存活就執行完,要么對象頭設置無鎖狀態,要么偏向其他線程)。
-
偏向鎖的關閉
- 偏向鎖在Java6和java7里默認是開啟的,但是它是應用程序啟動幾秒之后才會激活,可以通過-XX:BiasedLockingStartuoDelay=0 關閉延遲。通過 -XX:-UserBiasedLocking=false 關閉偏向鎖,那么線程就會默認進入輕量級鎖的狀態。
在偏向鎖的應用場景主要集中在競爭不激烈的情況下,通過使用偏向鎖可以減少其在CAS操作下的同步性能消耗,從而獲取性能的提升。
輕量級鎖
-
若一個線程已經獲取到了當前對象的鎖,這時第二個線程又開始嘗試爭搶該對象的鎖,由於該對象已經被第一次線程獲取到了,因此它是偏向鎖,而第二個線程在爭搶時,發現對象頭的Mark World已經是偏向鎖,但里面存儲的線程Id並不是自己(是第一個線程),那么它會進行CAS,來競爭鎖,這里存在兩種情況:
- 獲取鎖成功:那么它會直接將Mark World中的線程Id由第一個變成自己的,但是鎖標志還是偏向鎖。
- 獲取鎖失敗:則表示這時可能會有多個線程同時在嘗試爭搶該鎖,那么這時偏向鎖就會升級,升級為輕量級鎖。
-
上述情況如果獲取鎖失敗的情況:
- 自旋:當發生對Monitor競爭時,若持有者在很短的時間內釋放掉鎖,則那些在競爭的線程就可以稍微等一下(自旋),在持有者釋放鎖之后,競爭者就會獲得到monitor對象,從而避免了系統的阻塞(不用從用戶態切換到內核態進行阻塞等待)。不過當持有者只有鎖的時間比較久的時候超過了自旋的時間上限(這個jvm指定的時間點,也不能一直自旋下去浪費cpu資源),這個時候就會停止自旋進入阻塞狀態。總體思路就是:先自旋,不成功再去阻塞,盡可能的降低阻塞(用戶態和內核態的切換)的可能性,這對那些執行時間比較短的同步鎖性能得到了極大的提升。
- 在了解了自旋的原理之后,如果上述獲取鎖失敗了,就會進入自旋,自選失敗了就會進入阻塞狀態,這個時候的鎖就是重量級鎖,因為發生了用戶態到內核態的切換。
重量級鎖
- 就是沒升級之前的鎖的狀態,每次獲取鎖就要發生用戶態和內核態之間的切換。
各自的優缺點