文章目錄
-
-
- 一、簡介
- 二、Java對象頭中的Mark Word
- 三、偏向鎖
- 四、輕量級鎖
- 五、重量級鎖
- 六、自旋鎖
- 七、鎖更新過程
-
一、簡介
在講解這些鎖概念之前,我們要明确的是這些鎖不等同于Java API中的ReentratLock這種鎖,這些鎖是概念上的,是JDK1.6中為了對synchronized同步關鍵字進行優化而産生的的鎖機制。這些鎖的啟動和關閉政策可以通過設定JVM啟動參數來設定,當然在一般情況下,使用JVM預設的政策就可以了。
二、Java對象頭中的Mark Word
HotSpot中,Java的對象記憶體模型分為三部分,分别為對象頭、執行個體資料和對齊填充。而對象頭中分為兩部分,一部分是“Mark Word”(存儲對象自身的運作時資料,32bit或64bit,可複用);另一部分是指向它的類的中繼資料指針。
因為synchronized是Java對象的内置鎖,是以其優化政策(即偏向鎖等)的資訊都包含在Mark Word中,讓我們先看一下Mark Word的結構。
其中最重要的是“鎖标志位”和“是否偏向鎖”,鎖标志位代表了目前對象内置鎖的狀态,不同的鎖狀态下Mark Word存儲的資訊是不同的,是以稱為可複用。
三、偏向鎖
通俗的講,偏向鎖就是在運作過程中,對象的鎖偏向某個線程。即在開啟偏向鎖機制的情況下,某個線程獲得鎖,當該線程下次再想要獲得鎖時,不需要再獲得鎖(即忽略synchronized關鍵詞),直接就可以執行同步代碼,比較适合競争較少的情況。
偏向鎖的擷取流程:
(1)檢視Mark Word中偏向鎖的辨別以及鎖标志位,若是否偏向鎖為1且鎖标志位為01,則該鎖為可偏向狀态。
(2)若為可偏向狀态,則測試Mark Word中的線程ID是否與目前線程相同,若相同,則直接執行同步代碼,否則進入下一步。
(3)目前線程通過CAS操作競争鎖,若競争成功,則将Mark Word中線程ID設定為目前線程ID,然後執行同步代碼,若競争失敗,進入下一步。
(4)目前線程通過CAS競争鎖失敗的情況下,說明有競争。當到達全局安全點時之前獲得偏向鎖的線程被挂起,偏向鎖更新為輕量級鎖,然後被阻塞在安全點的線程繼續往下執行同步代碼。
偏向鎖的釋放流程:
偏向鎖隻有遇到其他線程嘗試競争偏向鎖時,持有偏向鎖狀态的線程才會釋放鎖,線程不會主動去釋放偏向鎖。偏向鎖的撤銷需要等待全局安全點(即沒有位元組碼正在執行),它會暫停擁有偏向鎖的線程,撤銷後偏向鎖恢複到未鎖定狀态或輕量級鎖狀态。
下面看一個實驗:
/** * * 開啟偏向鎖參數:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 耗時4000ms左右 * 警用偏向鎖參數:-XX:-UseBiasedLocking 耗時1900ms左右 */public class VectorTest {
public static void main(String[] args) {
long time1 = System.currentTimeMillis();
Vector<Integer> vector = new Vector<Integer>();
for (int i = 0; i < 100000000; i++){
vector.add(100);//add是synchronized操作 }
System.out.println(System.currentTimeMillis() - time1); }}
因為Vector是同步容器,其add操作是有synchronized修飾的,在JVM參數中設定開啟偏向鎖或禁用偏向鎖時,其耗時相差很大,說明偏向鎖對synchronized關鍵詞的優化在單個線程中或競争較少的線程中是很成功的。但是在多線程競争十分頻繁的情況下,偏向鎖不僅不能提高效率,反而會因為不斷地重新設定偏向線程ID等其他消耗而降低效率。
四、輕量級鎖
輕量級鎖不是用來替代傳統的重量級鎖的,而是在沒有多線程競争的情況下,使用輕量級鎖能夠減少性能消耗,但是當多個線程同時競争鎖時,輕量級鎖會膨脹為重量級鎖。
輕量級鎖的加鎖過程:
(1)當線程執行代碼進入同步塊時,若Mark Word為無鎖狀态,虛拟機先在目前線程的棧幀中建立一個名為Lock Record的空間,用于存儲目前對象的Mark Word的拷貝,官方稱之為“Dispalced Mark Word”,此時狀态如下圖:
(2)複制對象頭中的Mark Word到鎖記錄中。
(3)複制成功後,虛拟機将用CAS操作将對象的Mark Word更新為執行Lock Record的指針,并将Lock Record裡的owner指針指向對象的Mark Word。如果更新成功,則執行4,否則執行5。;
(4)如果更新成功,則這個線程擁有了這個鎖,并将鎖标志設為00,表示處于輕量級鎖狀态,此時狀态圖:
(5)如果更新失敗,虛拟機會檢查對象的Mark Word是否指向目前線程的棧幀,如果是則說明目前線程已經擁有這個鎖,可進入執行同步代碼。否則說明多個線程競争,輕量級鎖就會膨脹為重量級鎖,Mark Word中存儲重量級鎖(互斥鎖)的指針,後面等待鎖的線程也要進入阻塞狀态。
五、重量級鎖
即當有其他線程占用鎖時,目前線程會進入阻塞狀态。
synchronized
六、自旋鎖
CAS,又稱無鎖
在自旋狀态下,當一個線程A嘗試進入同步代碼塊,但是目前的鎖已經被線程B占有時,線程A不進入阻塞狀态,而是不停的空轉,等待線程B釋放鎖。如果鎖的線程能在很短時間内釋放資源,那麼等待競争鎖的線程就不需要做核心态和使用者态之間的切換進入阻塞狀态,隻需自旋,等持有鎖的線程釋放後即可立即擷取鎖,避免了使用者線程和核心的切換消耗。
自旋等待最大時間:線程自旋會消耗cpu,若自旋太久,則會讓cpu做太多無用功,是以要設定自旋等待最大時間。
優點:開啟自旋鎖後能減少線程的阻塞,在對于鎖的競争不激烈且占用鎖時間很短的代碼塊來說,能提升很大的性能,在這種情況下自旋的消耗小于線程阻塞挂起的消耗。
缺點:線上程競争鎖激烈,或持有鎖的線程需要長時間執行同步代碼塊的情況下,使用自旋會使得cpu做的無用功太多。
JDK1.6中,設定參數 -XX:+UseSpinning開啟。
JDK1.7後,由JVM自動控制。