jdk中synchronized加鎖原理以及新版本的優化

java中每個對象都可作爲鎖,鎖有四種級別,按照量級從輕到重分爲:無鎖、偏向鎖、輕量級鎖、重量級鎖。並且鎖只能升級不能降級。

在講這三個鎖之前,我先給大家講清楚自旋和對象頭的概念。

自旋

現在假設有這麼一個場景:有兩個線程A,B在競爭一個鎖,假設A拿到了,這個時候B被掛起阻塞,一直等待A釋放了鎖 B纔得到使用權。在操作系統中阻塞和喚醒是一個耗時操作,如果A在很短的時間內就釋放了鎖,當這個時間與阻塞喚醒比較起來更短的時候,我們將B掛起,其實不是一個最優的選擇。

自旋是指某線程需要獲取鎖,但該鎖已經被其他線程佔用時,該線程不會被掛起,而是在不斷的消耗CPU的時間,不停的試圖獲取鎖。雖然CPU的時間被消耗了,但是比線程下文切換時間要少。這個時候使用自旋是划算的。

如果是單核處理器,一般建議不要使用自旋鎖。因爲只有單個處理器,自旋佔用的時間片使得代價很高。而偏向鎖、輕量鎖、重量鎖也是一個鎖圍繞着如何使得程序運行的更加“划算”而進行改變的。

#對象頭
HotSpot虛擬機中,對象在內存中存儲的佈局可以分爲三塊區域:對象頭、實例數據和對齊填充。

HotSpot虛擬機的對象頭包括兩部分信息,第一部分用於存儲對象自身的運行時數據, 如哈希碼、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等等。

這部分數據的長度在32位和64位的虛擬機(暫不考慮開啓壓縮指針的場景)中分別爲32個和64個Bits,官方稱它爲“MarkWord”。
在32位的HotSpot虛擬機 中對象未被鎖定的狀態下,Mark Word的32個Bits空間中的25Bits用於存儲對象哈希碼,4Bits用於存儲對象分代年齡,2Bits用於存儲鎖標誌 位,1Bit固定爲0,在其他狀態(輕量級鎖定、重量級鎖定、GC標記、可偏向)下對象的存儲內容如下表所示。
在這裏插入圖片描述

偏向鎖

引入偏向鎖是爲了在無多線程競爭的情況下儘量減少不必要的輕量級鎖執行路徑,因爲輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令。
當只有一個線程去競爭鎖的時候,我們不需要阻塞,也不需要自旋,因爲只有一個線程在競爭,我們只要去判斷該偏向鎖中的ThreadID是否爲當前線程即可。如果是就執行同步代碼,不是就嘗試使用CAS修改ThreadID,修改成功執行同步代碼,不成功就將偏向鎖升級成輕量鎖。

輕量鎖

獲取輕量鎖的過程與偏向鎖不同,競爭鎖的線程首先需要拷貝對象頭中的Mark Word到幀棧的鎖記錄中。拷貝成功後使用CAS操作嘗試將對象的Mark Word更新爲指向當前線程的指針。如果這個更新動作成功了,那麼這個線程就擁有了該對象的鎖。如果更新失敗,那麼意味着有多個線程在競爭。
當競爭線程嘗試佔用輕量級鎖失敗多次之後(使用自旋)輕量級鎖就會膨脹爲重量級鎖,重量級線程指針指向競爭線程,競爭線程也會阻塞,等待輕量級線程釋放鎖後喚醒他。

重量鎖

重量級鎖的加鎖、解鎖過程和輕量級鎖差不多,區別是:競爭失敗後,線程阻塞,釋放鎖後,喚醒阻塞的線程,不使用自旋鎖,不會那麼消耗CPU,所以重量級鎖適合用在同步塊執行時間長的情況下。


作者:嘎裏給給
來源:CSDN
原文:https://blog.csdn.net/makecontral/article/details/79435933

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章