文章目錄
- 前言
- 一、偏向鎖
-
- 1. 什麼是偏向鎖
- 2. 偏向鎖的實作原理
-
- 2.1 對象頭
- 2.2 偏向鎖的擷取
- 2.3 偏向鎖的關閉
- 二、輕量級鎖
-
- 1. 什麼是輕量級鎖
- 2. 輕量級鎖的擷取
- 3. 輕量級鎖的解鎖
- 三、重量級鎖
-
- 1. 什麼是重量級鎖
- 2. 重量級鎖的擷取
- 四、鎖的更新與特點
-
- 1. 鎖的更新
- 2. 鎖的特點
-
- 1. 鎖的标志位
- 2. 鎖的對比
前言
最近整理了很多關于多線程的理論概念與底層實作,這裡我盡量的使用圖文結合的方式友善大家了解,如果有不清楚的地方也歡迎大家留言,我基本每天都在,可以在看到的第一時間回複。
一、偏向鎖
1. 什麼是偏向鎖
偏向鎖是JDK1.6提出的用與目前同步代碼不存在競争的情況下使用的。Hotspot虛拟機的設計者發現大多數情況下,鎖對象不僅不存在多個對象之間的競争,反而總是由同一個線程多次獲得,為了降低線程獲得鎖的代價而引入了偏向鎖。偏向鎖是指當一個鎖對象剛剛被建立的時候,它會偏向于第一個通路它的線程。此時線程修改對象頭中關于偏向鎖的标志位擷取偏向鎖。此後若不存線上程競争,該偏向線程再次通路鎖對象時不會觸發同步。 偏向鎖通過消除無資源競争下的同步源語來增加程式的運作效率。
2. 偏向鎖的實作原理
2.1 對象頭
在講偏向鎖的實作原理前我們首先要了解一下什麼是對象頭:
HotSport虛拟機為了提高針對不同執行個體對象的運作效率,增強面向對象的功能,将一個對象分為了三部分:對象頭(Header),執行個體資料(Instance Data),對齊填充(Padding)。
其中對象頭又分為三個部分:
markword
用于存儲對象自身運作時資料,如哈希碼(hashcode),GC分代年齡,鎖狀态标記,線程持有的鎖,偏向線程ID,偏向時間戳等,根據32/64位虛拟機不同,其大小常分為32bit或64bit。
klass
其主要用來表述對象指向它的類中繼資料指針,虛拟機通過這個指針來判斷目前對象是哪個類的執行個體。
數組長度
如果一個對象是數組,那麼對象頭還需要存儲數組長度
偏向鎖的實作主要應用了對象頭中的MarkWord,這裡我們看下對象頭(32位虛拟機)中的markword的存儲結構如何

2.2 偏向鎖的擷取
這裡還是按照慣例,先上一個關于偏向鎖擷取的流程圖:
下面我們就可以看圖寫小作文了:
當一個線程通路同步代碼塊的時候,會判斷此時的鎖對象是否偏向其他線程:
- 如果沒有偏向其他線程的話則會在鎖對象的對象頭和棧幀中存儲偏向鎖偏向的線程ID,以後進入和退出該同步塊時不需要再觸發CAS操作來加鎖和解鎖。
- 如果此時偏向其他線程,則需要通過CAS操作競争鎖,如果競争成功則修改偏向鎖偏向的線程ID,如果競争失敗,則表示線程間存在競争,當到達全局安全點會将獲得偏向鎖的線程挂起,偏向鎖更新為輕量級鎖。(撤銷偏向鎖會引起STW stop the world)
全局安全點:此時沒有任何正在執行的位元組碼
2.3 偏向鎖的關閉
偏向鎖在JDK 1.6 和JDK 1.7 中是預設開啟的,但是他在應用程式啟動幾秒後才會被加載,如果有必要的話可以通過修改JVM參數來關閉延遲: -XX:BiasedLockingStartupDelay = 0 。當然,如果你能夠預估到線程間的競争比較激烈也可以通過參數直接關閉偏向鎖的使用:-XX:UserBiasedLocking = false ,這樣程式預設進入輕量級鎖狀态。
二、輕量級鎖
1. 什麼是輕量級鎖
JDK 1.6 之後為了減少擷取鎖和釋放鎖帶來的性能開銷,引入了輕量級鎖。輕量級鎖應用于線程之間存在競争但競争不激烈的情況,其主要通過線程自旋代替阻塞來提升程式的響應速度。
2. 輕量級鎖的擷取
輕量級鎖是由偏向鎖更新而成,當然如果程式禁用了偏向鎖,當線程第一次通路同步代碼塊的時候預設會獲得輕量級鎖。這裡針對輕量級鎖的擷取流程我們還是通過一個簡單的流程圖說明下:
以下描述出自《Java并發程式設計的藝術 》
線程在執行同步代碼塊之前,JVM會先在目前線程的棧幀中建立用于存儲鎖記錄的空間(Lock Record)并将對象頭中的Markword拷貝到lockRecord中,官方稱為Displaced Mark Word(替換标志位)。然後線程将嘗試通過CAS将對象頭中的Mark Word替換為指向所記錄的指針。如果成功,則目前線程獲得鎖,如果失敗表示有其他線程在競争鎖,目前線程自旋等待下次擷取鎖的機會。
3. 輕量級鎖的解鎖
輕量級鎖解鎖時,會使用原子性的CAS操作将Displaced Mark Word替換到對象頭,如果成功則表示沒有競争,如果失敗則表示目前鎖存在競争,此時輕量級鎖會膨脹為重量級鎖。
三、重量級鎖
1. 什麼是重量級鎖
在Java中提供了一種原子性内置鎖synchronized,他依賴于将内置鎖抽象為moniter(螢幕鎖)實作的。在JDK1.6之前使用synchronized的代價是非常龐大的,它依賴于直接對作業系統中的互斥量進行操作,其實作鎖功能的代價是挂起和喚醒線程都需要通過作業系統核心完成,十分消耗性能,是以這種鎖也被稱為重量級鎖。
2. 重量級鎖的擷取
重量級鎖通常為輕量級鎖更新而來,一旦更新為重量級鎖,所有競争鎖的線程需要同步阻塞等待競争鎖。這裡還是按照慣例畫一個簡單的流程圖:
四、鎖的更新與特點
JDK1.6設計者針對synchronized做了一系列優化,包括引入偏向鎖,自适應自旋鎖,輕量級鎖解決不同線程競争程度下synchronized的加鎖政策。現在synchronized的性能已經可以和reentrantlock有一拼,甚至java官方都在推薦使用synchronized關鍵字作為同步政策。那麼很多人會好奇,不同的鎖是如何進行轉化的,并且不同的鎖的更新過程如何?今天的最後我們就來總結一下鎖的相關特點。
1. 鎖的更新
鎖的更新過程還是通過一個簡單的流程圖說明下:
看到自己畫的圖,滋滋滋我是真的厲害,哈哈哈哈哈哈哈哈哈。
上圖基本已經概括了鎖更新的完整過程,其中更新條件也已經注明,如果有不明白的也可以在評論區留言或者私信我~
2. 鎖的特點
1. 鎖的标志位
在鎖的更新過程中,提到了一個鎖的标志位的概念,鎖的标志位其實存儲在對象頭的mark word中,這裡可以參考下《Java并發程式設計藝術》中對于32位虛拟機的對象頭标志位狀态變化表(這個表實在是懶得畫了,直接粘的書中的表):
2. 鎖的對比
這裡針對鎖的優缺點做個簡單的闡述:
偏向鎖輕量級鎖:
- 優點: 加鎖和釋放鎖不需要額外的操作,和執行非同步方法的差距僅在納秒級
- 缺點: 偏向鎖更新為輕量級鎖時會引起stw造成卡頓
- 适用場景:隻有一個線程通路同步塊
重量級鎖:
- 優點: 不會造成線程堵塞,無需等待線程的休眠和喚醒,提高了程式的響應速度。
- 缺點: 過多的自旋會帶來額外的cpu消耗
- 适用場景:追求響應時間,同步塊執行速度非常快
- 優點:線程競争無需自旋,不會消耗CPU
- 缺點:線程阻塞,響應時間慢
- 适用場景:追求吞吐量,同步塊執行較長
好了,鎖的更新與對比想過的知識點就到這裡了,有收獲的小夥伴希望可以一鍵三連給部落客個鼓勵!
祝三連的小夥伴可以升職加薪迎娶白富美!!!!