執行緒安全和鎖02
轉https://www.cnblogs.com/mingyueyy/p/13054296.html
1 輕量級鎖和重量級鎖簡要說明
執行緒排程本來是由作業系統來管理的。現在,作業系統上跑了一個虛擬機器JVM,JVM可以啟動JVM級別的執行緒,實際上還是落實到作業系統級別的執行緒。
JVM自己能夠搞定的鎖叫輕量級鎖
交給作業系統解決的鎖叫重量級鎖
1.1 輕量級鎖
它是依靠CAS實現的
https://www.cnblogs.com/jthr/p/14700940.html
1.2 重量級鎖
它是依靠作業系統來解決的。它同對於鎖的管理最重要的叫排隊,每個鎖都有一個等待佇列。
輕量級鎖是轉著圈不斷嘗試,重量級鎖是排隊等待。
1.3 它們比較優劣
當等待的執行緒很多,等待的時間長(併發高,臨界區長)的時候,適用於重量級鎖,因為輕量級鎖的所有等待執行緒在不斷的嘗試,消耗資源),併發不高,臨界區不長適合輕量級鎖。兩者在不同的情況下各有優勢。
2 鎖的四種狀態和升級過程
2.1、簡介
鎖的狀態總共有四種,級別由低到高依次為:無鎖、偏向鎖、輕量級鎖、重量級鎖。
這四種鎖狀態分別代表什麼,為什麼會有鎖升級?
其實在 JDK 1.6之前,synchronized 還是一個重量級鎖,是一個效率比較低下的鎖,但是在JDK 1.6後,Jvm為了提高鎖的獲取與釋放效率對(synchronized )進行了優化,引入了 偏向鎖 和 輕量級鎖
2.2、鎖的四種狀態
在 synchronized
最初的實現方式是 “阻塞或喚醒一個Java執行緒需要作業系統切換CPU狀態來完成,這種狀態切換需要耗費處理器時間,如果同步程式碼塊中內容過於簡單,這種切換的時間可能比使用者程式碼執行的時間還長”,這種方式就是 synchronized
synchronized
效率低下的原因,JDK6中為了減少獲得鎖和釋放鎖帶來的效能消耗,引入了“偏向鎖”和“輕量級鎖”。
所以目前鎖狀態一種有四種,從級別由低到高依次是:無鎖、偏向鎖,輕量級鎖,重量級鎖,鎖狀態只能升級,不能降級
如圖所示:
2.3、鎖狀態的思路以及特點
鎖狀態 | 儲存內容 | 標誌位 |
---|---|---|
無鎖 | 物件的hashCode、物件分代年齡、是否是偏向鎖(0) | 01 |
偏向鎖 | 偏向執行緒ID、偏向時間戳、物件分代年齡、是否是偏向鎖(1) | 01 |
輕量級鎖 | 指向棧中鎖記錄的指標 | 00 |
重量級鎖 | 指向互斥量的指標 | 11 |
2.4、鎖對比
鎖 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
偏向鎖 | 加鎖和解鎖不需要額外的消耗,和執行非同步方法相比僅存在納秒級的差距 | 如果執行緒間存在鎖競爭,會帶來額外的鎖撤銷的消耗 | 適用於只有一個執行緒訪問同步塊場景 |
輕量級鎖 | 競爭的執行緒不會阻塞,提高了程式的響應速度 | 如果始終得不到索競爭的執行緒,使用自旋會消耗CPU | 追求響應速度,同步塊執行速度非常快 |
重量級鎖 | 執行緒競爭不使用自旋,不會消耗CPU | 執行緒阻塞,響應時間緩慢 | 追求吞吐量,同步塊執行速度較慢 |
2.5 Java 物件頭
我們以 Hotspot 虛擬機器為例,Hopspot 物件頭主要包括兩部分資料:Mark Word(標記欄位) 和 Klass Pointer(型別指標)
Mark Word:預設儲存物件的HashCode,分代年齡和鎖標誌位資訊。這些資訊都是與物件自身定義無關的資料,所以Mark Word被設計成一個非固定的資料結構以便在極小的空間記憶體儲存儘量多的資料。它會根據物件的狀態複用自己的儲存空間,也就是說在執行期間Mark Word裡儲存的資料會隨著鎖標誌位的變化而變化。
Klass Point:物件指向它的類元資料的指標,虛擬機器通過這個指標來確定這個物件是哪個類的例項。
在上面中我們知道了,synchronized
用的鎖是存在Java物件頭裡的,那麼具體是存在物件頭哪裡呢?答案是:存在鎖物件的物件頭的Mark Word中,那麼MarkWord在物件頭中到底長什麼樣,它到底儲存了什麼呢?
在64位的虛擬機器中:
在32位的虛擬機器中:
下面我們以 32位虛擬機器為例,來看一下其 Mark Word 的位元組具體是如何分配的
無鎖:物件頭開闢 25bit 的空間用來儲存物件的 hashcode ,4bit 用於存放物件分代年齡,1bit 用來存放是否偏向鎖的標識位,2bit 用來存放鎖標識位為01
偏向鎖: 在偏向鎖中劃分更細,還是開闢 25bit 的空間,其中23bit 用來存放執行緒ID,2bit 用來存放 Epoch,4bit 存放物件分代年齡,1bit 存放是否偏向鎖標識, 0表示無鎖,1表示偏向鎖,鎖的標識位還是01
輕量級鎖:在輕量級鎖中直接開闢 30bit 的空間存放指向棧中鎖記錄的指標,2bit 存放鎖的標誌位,其標誌位為00
重量級鎖: 在重量級鎖中和輕量級鎖一樣,30bit 的空間用來存放指向重量級鎖的指標,2bit 存放鎖的標識位,為11
GC標記: 開闢30bit 的記憶體空間卻沒有佔用,2bit 空間存放鎖標誌位為11。
其中無鎖和偏向鎖的鎖標誌位都是01,只是在前面的1bit區分了這是無鎖狀態還是偏向鎖狀態
2.6 鎖的分類
2.6.1 無鎖
無鎖是指沒有對資源進行鎖定,所有的執行緒都能訪問並修改同一個資源,但同時只有一個執行緒能修改成功。
無鎖的特點是修改操作會在迴圈內進行,執行緒會不斷的嘗試修改共享資源。如果沒有衝突就修改成功並退出,否則就會繼續迴圈嘗試。如果有多個執行緒修改同一個值,必定會有一個執行緒能修改成功,而其他修改失敗的執行緒會不斷重試直到修改成功。
2.6.2 偏向鎖
初次執行到synchronized程式碼塊的時候,鎖物件變成偏向鎖(通過CAS修改物件頭裡的鎖標誌位),字面意思是“偏向於第一個獲得它的執行緒”的鎖。執行完同步程式碼塊後,執行緒並不會主動釋放偏向鎖。當第二次到達同步程式碼塊時,執行緒會判斷此時持有鎖的執行緒是否就是自己(持有鎖的執行緒ID也在物件頭裡),如果是則正常往下執行。由於之前沒有釋放鎖,這裡也就不需要重新加鎖。如果自始至終使用鎖的執行緒只有一個,很明顯偏向鎖幾乎沒有額外開銷,效能極高。
當一個執行緒訪問同步程式碼塊並獲取鎖時,會在 Mark Word 裡儲存鎖偏向的執行緒 ID。線上程進入和退出同步塊時不再通過 CAS 操作來加鎖和解鎖,而是檢測 Mark Word 裡是否儲存著指向當前執行緒的偏向鎖。輕量級鎖的獲取及釋放依賴多次 CAS 原子指令,而偏向鎖只需要在置換 ThreadID 的時候依賴一次 CAS 原子指令即可。
偏向鎖只有遇到其他執行緒嘗試競爭偏向鎖時,持有偏向鎖的執行緒才會釋放鎖,執行緒是不會主動釋放偏向鎖的。
關於偏向鎖的撤銷,需要等待全域性安全點,即在某個時間點上沒有位元組碼正在執行時,它會先暫停擁有偏向鎖的執行緒,然後判斷鎖物件是否處於被鎖定狀態。如果執行緒不處於活動狀態,則將物件頭設定成無鎖狀態,並撤銷偏向鎖,恢復到無鎖(標誌位為01)或輕量級鎖(標誌位為00)的狀態
2.6.3 輕量級鎖(自旋鎖)
輕量級鎖是指當鎖是偏向鎖的時候,卻被另外的執行緒所訪問,此時偏向鎖就會升級為輕量級鎖,其他執行緒會通過自旋的形式嘗試獲取鎖,執行緒不會阻塞,從而提高效能。
輕量級鎖的獲取主要由兩種情況:
① 當關閉偏向鎖功能時;
② 由於多個執行緒競爭偏向鎖導致偏向鎖升級為輕量級鎖。
一旦有第二個執行緒加入鎖競爭,偏向鎖就升級為輕量級鎖(自旋鎖)。這裡要明確一下什麼是鎖競爭:如果多個執行緒輪流獲取一個鎖,但是每次獲取鎖的時候都很順利,沒有發生阻塞,那麼就不存在鎖競爭。只有當某執行緒嘗試獲取鎖的時候,發現該鎖已經被佔用,只能等待其釋放,這才發生了鎖競爭。
在輕量級鎖狀態下繼續鎖競爭,沒有搶到鎖的執行緒將自旋,即不停地迴圈判斷鎖是否能夠被成功獲取。獲取鎖的操作,其實就是通過CAS修改物件頭裡的鎖標誌位。先比較當前鎖標誌位是否為“釋放”,如果是則將其設定為“鎖定”,比較並設定是原子性發生的。這就算搶到鎖了,然後執行緒將當前鎖的持有者資訊修改為自己。
長時間的自旋操作是非常消耗資源的,一個執行緒持有鎖,其他執行緒就只能在原地空耗CPU,執行不了任何有效的任務,這種現象叫做忙等(busy-waiting)。如果多個執行緒用一個鎖,但是沒有發生鎖競爭,或者發生了很輕微的鎖競爭,那麼synchronized就用輕量級鎖,允許短時間的忙等現象。這是一種折衷的想法,短時間的忙等,換取執行緒在使用者態和核心態之間切換的開銷。
2.6.4 重量級鎖
重量級鎖顯然,此忙等是有限度的(有個計數器記錄自旋次數,預設允許迴圈10次,可以通過虛擬機器引數更改)。如果鎖競爭情況嚴重,某個達到最大自旋次數的執行緒,會將輕量級鎖升級為重量級鎖(依然是CAS修改鎖標誌位,但不修改持有鎖的執行緒ID)。當後續執行緒嘗試獲取鎖時,發現被佔用的鎖是重量級鎖,則直接將自己掛起(而不是忙等),等待將來被喚醒。
重量級鎖是指當有一個執行緒獲取鎖之後,其餘所有等待獲取該鎖的執行緒都會處於阻塞狀態(排隊等待)。
簡言之,就是所有的控制權都交給了作業系統,由作業系統來負責執行緒間的排程和執行緒的狀態變更。而這樣會出現頻繁地對執行緒執行狀態的切換,執行緒的掛起和喚醒,從而消耗大量的系統資
2.7 鎖的升級過程