介紹
首先,我們需要明確一點:偏向級鎖、輕量級鎖、重量級鎖只針對synchronized?
鎖的狀態總共有四種,級別由低到高依次為:無鎖、偏向鎖、輕量級鎖、重量級鎖。
這四種鎖狀態分別代表什么,為什么會有鎖升級?其實在 JDK 1.6之前,synchronized 還是一個重量級鎖,是一個效率比較低下的鎖,但是在JDK 1.6后,Jvm為了提高鎖的獲取與釋放效率對synchronized?進行了優化,引入了?偏向鎖 和 輕量級鎖?,從此以后鎖的狀態就有了四種(無鎖、偏向鎖、輕量級鎖、重量級鎖),并且四種狀態會隨著競爭的情況逐漸升級,而且是不可逆的過程,即不可降級,也就是說只能進行鎖升級(從低級別到高級別),不能鎖降級(高級別到低級別)。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。
?
1.無鎖?
無鎖是指沒有對資源進行鎖定,所有的線程都能訪問并修改同一個資源,但同時只有一個線程能修改成功。
無鎖的特點是修改操作會在循環內進行,線程會不斷的嘗試修改共享資源。如果沒有沖突就修改成功并退出,否則就會繼續循環嘗試。如果有多個線程修改同一個值,必定會有一個線程能修改成功,而其他修改失敗的線程會不斷重試直到修改成功。
?
2.偏向鎖
?顧名思義,偏向某一個線程,當線程數目不多的時候,由于反復獲取鎖會使得我們的運行效率下降,于是出現了偏向級鎖。JVM使用CAS操作把線程ID記錄到對象的Mark Word當中,并修改標識位,name當前線程就擁有了這把鎖。
?
? 偏向級鎖不需要操作系統的介入,JVM使用CAS操作將線程ID放入對象的Mark Word字段中,于是線程獲得了鎖,可以執行synchronized代碼塊的內容,當線程再次執行到這個synchronized的時候,JVM通過鎖對象的Mark Word判斷 :當前線程ID還存在,還持有這個對象的鎖,于是就可以繼續進入臨界區執行,而不需要再次獲得鎖
初次執行到synchronized代碼塊的時候,鎖對象變成偏向鎖(通過CAS修改對象頭里的鎖標志位),字面意思是“偏向于第一個獲得它的線程”的鎖。執行完同步代碼塊后,線程并不會主動釋放偏向鎖。當第二次到達同步代碼塊時,線程會判斷此時持有鎖的線程是否就是自己(持有鎖的線程ID也在對象頭里),如果是則正常往下執行。由于之前沒有釋放鎖,這里也就不需要重新加鎖。如果自始至終使用鎖的線程只有一個,很明顯偏向鎖幾乎沒有額外開銷,性能極高。
偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程是不會主動釋放偏向鎖的。?
偏向鎖是指當一段同步代碼一直被同一個線程所訪問時,即不存在多個線程的競爭時,那么該線程在后續訪問時便會自動獲得鎖,從而降低獲取鎖帶來的消耗,即提高性能。
?3.輕量級鎖
輕量級鎖是指當鎖是偏向鎖的時候,卻被另外的線程所訪問,此時偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,線程不會阻塞,從而提高性能。
輕量級鎖的獲取主要由兩種情況:
① 當關閉偏向鎖功能時;
② 由于多個線程競爭偏向鎖導致偏向鎖升級為輕量級鎖。
一旦有第二個線程加入鎖競爭,偏向鎖就升級為輕量級鎖(自旋鎖)。這里要明確一下什么是鎖競爭:如果多個線程輪流獲取一個鎖,但是每次獲取鎖的時候都很順利,沒有發生阻塞,那么就不存在鎖競爭。只有當某線程嘗試獲取鎖的時候,發現該鎖已經被占用,只能等待其釋放,這才發生了鎖競爭。
在輕量級鎖狀態下繼續鎖競爭,沒有搶到鎖的線程將自旋,即不停地循環判斷鎖是否能夠被成功獲取。獲取鎖的操作,其實就是通過CAS修改對象頭里的鎖標志位。先比較當前鎖標志位是否為“釋放”,如果是則將其設置為“鎖定”,比較并設置是原子性發生的。這就算搶到鎖了,然后線程將當前鎖的持有者信息修改為自己。
長時間的自旋操作是非常消耗資源的,一個線程持有鎖,其他線程就只能在原地空耗CPU,執行不了任何有效的任務,這種現象叫做忙等(busy-waiting)。如果多個線程用一個鎖,但是沒有發生鎖競爭,或者發生了很輕微的鎖競爭,那么synchronized就用輕量級鎖,允許短時間的忙等現象。這是一種折衷的想法,短時間的忙等,換取線程在用戶態和內核態之間切換的開銷。
?
?4.重量級鎖?
當多個線程競爭同一個鎖時,會導致除鎖的擁有者外,其余線程都會自旋,這將導致自旋次數過多,cpu效率下降,所以會將鎖升級為重量級鎖。?
重量級鎖顯然,此忙等是有限度的(有個計數器記錄自旋次數,默認允許循環10次,可以通過虛擬機參數更改)。如果鎖競爭情況嚴重,某個達到最大自旋次數的線程,會將輕量級鎖升級為重量級鎖(依然是CAS修改鎖標志位,但不修改持有鎖的線程ID)。當后續線程嘗試獲取鎖時,發現被占用的鎖是重量級鎖,則直接將自己掛起(而不是忙等),等待將來被喚醒。
重量級鎖是指當有一個線程獲取鎖之后,其余所有等待獲取該鎖的線程都會處于阻塞狀態。
?