一、基礎(chǔ)概念
1、鎖定義
如果某一個資源被多個線程共享,為了避免因?yàn)橘Y源搶占導(dǎo)致資源數(shù)據(jù)錯亂,我們需要對線程進(jìn)行同步,那么synchronized就是實(shí)現(xiàn)線程同步的關(guān)鍵字。
2、鎖特性(線程3要素(除可重復(fù)性))
- 原子性:一個操作要么全部發(fā)生,要么全部不發(fā)生,i++不具備原子性。
- 可見性:加鎖,釋放鎖之前會將對變量的修改刷新到主存當(dāng)中,或直接操作主存。
- 有序性:有序性指程序執(zhí)行的順序按照代碼先后執(zhí)行。
- 可重復(fù)性:一個線程擁有了鎖仍然還可以重復(fù)申請?jiān)撴i。
3、鎖類型
synchronized可以修飾方法、代碼塊,但是歸根結(jié)底它上鎖的資源只有兩類:一個是對象,一個是類
類上鎖:同步塊、靜態(tài)方法
對象上鎖:同步塊、成員方法
二、鎖實(shí)現(xiàn)原理
1、測試類
/**
* 歸根結(jié)底它上鎖的資源只有兩類:一個是對象,一個是類。
*/
public class SynchronizedTest {
public static int i=0;
SynchronizedTest test = new SynchronizedTest();
/**
* 對成員函數(shù)加鎖,必須獲得該類的實(shí)例對象的鎖才能進(jìn)入同步塊
*/
public synchronized void test1() {
i++;
}
/**
* 對靜態(tài)方法加鎖,必須獲得該類的鎖才能進(jìn)入同步塊,本文暫不關(guān)注該方法
*/
public static synchronized void test2() {
System.out.println("Hello static test2");
}
public void test3() {
/**
* 必須獲得類鎖
*/
synchronized(SynchronizedTest.class) {
System.out.println("Hello test3");
}
/**
* 必須獲得對象鎖, 本文暫不關(guān)注該代碼塊
*/
synchronized(test) {
System.out.println("Hello test3-2");
}
}
}
2、查看字節(jié)碼文件
// 編譯生成字節(jié)碼文件
javac SynchronizedTest.java
// 反編譯查看字節(jié)碼文件
javap -verbose SynchronizedTest.class
3、同步塊的實(shí)現(xiàn)
由monitorenter指令進(jìn)入,然后monitorexit釋放鎖,在執(zhí)行monitorenter之前需要嘗試獲取鎖,如果這個對象沒有被鎖定,或者當(dāng)前線程已經(jīng)擁有了這個對象的鎖,那么就把鎖的計(jì)數(shù)器加1。當(dāng)執(zhí)行monitorexit指令時,鎖的計(jì)數(shù)器也會減1。
4、同步方法
flags里面多了一個ACC_SYNCHRONIZED標(biāo)志,這標(biāo)志用來告訴JVM這是一個同步方法,在進(jìn)入該方法之前先獲取相應(yīng)的鎖,鎖的計(jì)數(shù)器加1,方法結(jié)束后計(jì)數(shù)器-1,如果獲取失敗就阻塞住,直到該鎖被釋放。
三、鎖底層實(shí)現(xiàn)原理
鎖存放在對象頭的Mark Word區(qū)域中。
鎖總共四個狀態(tài):無鎖狀態(tài)、偏向鎖、輕量級鎖、重量級鎖,其中無鎖就是一種狀態(tài)了。鎖的類型和狀態(tài)在對象頭Mark Word中都有記錄。
每個對象都存在著一個monitor與之關(guān)聯(lián),當(dāng)線程試圖獲取對象鎖時自動生成,但當(dāng)一個monitor被某個線程持有后,它便處于鎖定狀態(tài),Mark Word會記錄鎖定狀態(tài)和線程id,monitor中的_owner標(biāo)志會指向持有monitor的線程同時monitor中的計(jì)數(shù)器count加1。
總結(jié):
對象頭有Mark Word區(qū)域,當(dāng)線程視圖獲取對象鎖的時候,會生成一個monitor與對象關(guān)聯(lián)。線程獲取鎖其實(shí)就是獲取對象的monitor;當(dāng)一個monitor被線程持有以后,monitor中的_owner標(biāo)志會指向持有monitor的線程,同時monitor中的計(jì)數(shù)器count加1。Mark Word區(qū)域會記錄鎖定狀態(tài)、鎖的類型和線程id。
四、鎖優(yōu)化
鎖升級方向:無鎖——>偏向鎖——>輕量級鎖——>重量級鎖,并且方向不可逆。
1、偏向鎖:
在大多數(shù)情況下,鎖不存在多線程競爭,總是由同一線程多次獲得,那么此時就是偏向鎖。
如果一個線程獲得了鎖,那么鎖就進(jìn)入偏向模式,此時Mark Word的結(jié)構(gòu)也就變?yōu)槠蜴i結(jié)構(gòu),當(dāng)該線程再次請求鎖時,無需再做任何同步操作,即獲取鎖的過程只需要檢查Mark Word的鎖標(biāo)記位為偏向鎖以及當(dāng)前線程ID等于Mark Word的ThreadID即可,這樣就省去了大量有關(guān)鎖申請的操作。
2、輕量級鎖:
輕量級鎖是由偏向鎖升級而來,當(dāng)存在第二個線程申請同一個鎖對象時,偏向鎖就會立即升級為輕量級鎖。
3、重量級鎖:
重量級鎖是由輕量級鎖升級而來,當(dāng)同一時間有多個線程競爭鎖時,鎖就會被升級成重量級鎖,此時其申請鎖帶來的開銷也就變大。重量級鎖一般使用場景會在追求吞吐量,同步塊或者同步方法執(zhí)行時間較長的場景。
4、鎖消除:
消除鎖是虛擬機(jī)另外一種鎖的優(yōu)化,這種優(yōu)化更徹底,在JIT編譯時,對運(yùn)行上下文進(jìn)行掃描,去除不可能存在競爭的鎖。比如局部變量的加鎖。
5、鎖粗化:
鎖粗化是虛擬機(jī)對另一種極端情況的優(yōu)化處理,通過擴(kuò)大鎖的范圍,避免反復(fù)加鎖和釋放鎖。比如把for循環(huán)內(nèi)部的鎖擴(kuò)大到for循環(huán)外部。
6、自旋鎖與自適應(yīng)自旋鎖
輕量級鎖失敗后,虛擬機(jī)為了避免線程真實(shí)地在操作系統(tǒng)層面掛起,還會進(jìn)行一項(xiàng)稱為自旋鎖的優(yōu)化手段。
6.1、自旋鎖
許多情況下,共享數(shù)據(jù)的鎖定狀態(tài)持續(xù)時間較短,切換線程不值得,通過讓線程執(zhí)行循環(huán)等待鎖的釋放,不讓出CPU。如果得到鎖,就順利進(jìn)入臨界區(qū)。如果還不能獲得鎖,那就會將線程在操作系統(tǒng)層面掛起,這就是自旋鎖的優(yōu)化方式。但是它也存在缺點(diǎn):如果鎖被其他線程長時間占用,一直不釋放CPU,會帶來許多的性能開銷。
6.2、自適應(yīng)自旋鎖
這種相當(dāng)于是對上面自旋鎖優(yōu)化方式的進(jìn)一步優(yōu)化,它的自旋的次數(shù)不再固定,其自旋的次數(shù)由前一次在同一個鎖上自旋并成功獲取鎖的擁有者的自旋次數(shù)來決定,這就解決了自旋鎖帶來的缺點(diǎn)。