本節(jié)內(nèi)容:
java鎖介紹
偏向鎖鱼冀、輕量級(jí)鎖报破、重量級(jí)鎖
這三種鎖特指synchronized鎖的狀態(tài),通過對(duì)象頭中的mark work字段表示鎖狀態(tài)千绪。
偏向鎖:
自始至終充易,對(duì)這把鎖都不存在競(jìng)爭(zhēng),只需要做個(gè)標(biāo)記翘紊,這就是偏向鎖蔽氨,每個(gè)對(duì)象都是一個(gè)內(nèi)置鎖(內(nèi)置鎖是可重入鎖),一個(gè)對(duì)象被初始化后帆疟,還沒有任何線程來獲取它的鎖時(shí)鹉究,那么它就是可偏向的,當(dāng)有線程來訪問并嘗試獲取鎖的時(shí)候踪宠,他就會(huì)把這個(gè)線程記錄下來自赔,以后如果獲取鎖的線程正式偏向鎖的擁有者,就可以直接獲得鎖柳琢,偏向鎖性能最好绍妨。
輕量級(jí)鎖:
輕量級(jí)鎖是指原來是偏向鎖的時(shí)候润脸,這時(shí)被另外一個(gè)線程訪問,存在鎖競(jìng)爭(zhēng)他去,那么偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖毙驯,線程會(huì)通過自旋的形式嘗試獲取鎖,而不會(huì)陷入阻塞灾测。
重量級(jí)鎖:
重量級(jí)鎖是互斥鎖爆价,主要是利用操作系統(tǒng)的同步機(jī)制實(shí)現(xiàn)的,當(dāng)多個(gè)線程直接有并發(fā)訪問的時(shí)候媳搪,且鎖競(jìng)爭(zhēng)時(shí)間長(zhǎng)的時(shí)候铭段,輕量級(jí)鎖不能滿足需求,鎖就升級(jí)為重量級(jí)鎖秦爆,重量級(jí)鎖會(huì)使得其他拿不到鎖的線程陷入阻塞狀態(tài)序愚,重量級(jí)鎖的開銷相對(duì)較大。
可重入鎖等限、非可重入鎖
可重入鎖:
可重入鎖的意思是如果當(dāng)前線程已經(jīng)持有了這把鎖爸吮,能再不釋放的鎖的情況下再次獲得這把鎖,如果一個(gè)線程試圖獲取它已經(jīng)持有的鎖望门,那么這個(gè)請(qǐng)求會(huì)成功拗胜,每個(gè)鎖關(guān)聯(lián)一個(gè)獲取計(jì)數(shù)值和一個(gè)所有者線程,當(dāng)計(jì)數(shù)值為0的時(shí)候怒允,認(rèn)為沒有線程持有該鎖,當(dāng)線程請(qǐng)求一個(gè)未被持有的鎖的時(shí)候锈遥,JVM將記下鎖的持有者纫事,并且計(jì)數(shù)值置為1,如果同一個(gè)線程再次獲取該鎖的時(shí)候所灸,計(jì)數(shù)值將遞增丽惶,
不可重入鎖:
同理不可重入鎖就是指雖然當(dāng)前線程已經(jīng)持有了這把鎖,但是如果想要再次獲得這把鎖爬立,必須要先釋放鎖后才能再次嘗試獲取钾唬。
共享鎖、獨(dú)占鎖
共享鎖:
共享鎖就是我們可以同一把鎖被多個(gè)線程同時(shí)獲得侠驯,最典型的就是讀寫鎖中的讀鎖抡秆。
獨(dú)占鎖:
同理,獨(dú)占鎖就是線程只能對(duì)一個(gè)線程持有吟策,類比讀寫鎖中的寫鎖儒士,
公平鎖、非公平鎖
公平鎖:
公平鎖就是如果線程當(dāng)前拿不到這把鎖檩坚,那么線程就都會(huì)進(jìn)入等待着撩,開始排隊(duì)诅福,在等待隊(duì)列里等待時(shí)間長(zhǎng)的線程就會(huì)優(yōu)先拿到這把鎖,先來先得拖叙。
非公平鎖:
非公平鎖是指在一定的情況下氓润,某些線程會(huì)忽略掉已經(jīng)在排隊(duì)的線程,發(fā)生插隊(duì)的情況薯鳍。
悲觀鎖咖气、樂觀鎖
悲觀鎖:
悲觀鎖顧名思義,比較悲觀辐啄,悲觀鎖認(rèn)為如果不鎖住這個(gè)共享資源采章,別的線程就回來競(jìng)爭(zhēng),就會(huì)造成數(shù)據(jù)結(jié)果錯(cuò)誤壶辜,所以在獲取共享資源前悯舟,必須要先拿到鎖,以便達(dá)到“獨(dú)占”的狀態(tài)砸民,抵怎,讓其他線程無法訪問該數(shù)據(jù),這樣就不會(huì)發(fā)生數(shù)據(jù)錯(cuò)誤岭参。常見的悲觀鎖例如synchronized關(guān)鍵字反惕、Lock接口
樂觀鎖:
同理樂觀鎖是相對(duì)悲觀鎖而言的,樂觀鎖就是比較樂觀了演侯,它認(rèn)為一般情況下數(shù)據(jù)不會(huì)發(fā)生沖突姿染,只有在數(shù)據(jù)進(jìn)行更新的時(shí)候,才會(huì)對(duì)比數(shù)據(jù)在被當(dāng)前線程更新期間有咩有被修改過秒际,如果沒有被修改過悬赏,則可以正常更新數(shù)據(jù),如果數(shù)據(jù)發(fā)生過修改和預(yù)期不一樣娄徊,那么本次更新操作就不能成功闽颇,所以可以放棄這次更新或者選擇報(bào)錯(cuò)、重試等策略寄锐。常見的樂觀鎖例如:各種原子類
自旋鎖兵多、非自旋鎖
自旋鎖:
自旋鎖是指如果線程拿不到鎖,那么線程并不直接陷入阻塞或者釋放CPU資源而是開始自我旋轉(zhuǎn)橄仆,不停的嘗試獲取鎖剩膘,這個(gè)循環(huán)的過程成為自旋。
非自旋鎖:
非自旋鎖就是沒有自旋的過程沿癞,如果拿不到鎖就直接放棄或者進(jìn)行其他邏輯處理援雇,比如排隊(duì)、阻塞椎扬。
可中斷鎖惫搏、不可中斷鎖
可中斷鎖:
可中斷鎖指在獲取鎖的過程中具温,可以中斷鎖之后去做其他事情,不需要一直等到獲取到鎖才繼續(xù)處理
不可中斷鎖:
synchronized是不可中斷鎖筐赔,就是指一旦申請(qǐng)了鎖铣猩,只能等到拿到鎖以后才能進(jìn)行其他的邏輯處理。
synchronized鎖介紹
什么是synchronized鎖
java中每個(gè)對(duì)象中都持有一把鎖與之關(guān)聯(lián)茴丰,控制著對(duì)象的synchronized代碼达皿,想要執(zhí)行對(duì)象的synchronized代碼,
必須先獲得對(duì)象的鎖贿肩,這個(gè)鎖就是對(duì)象的Monitor鎖峦椰,synchronized實(shí)現(xiàn)加鎖解鎖就是利用Monitor鎖實(shí)現(xiàn)的。
獲取Monitor鎖的唯一方式是進(jìn)入由這個(gè)鎖保護(hù)的同步代碼塊或者同步方法中汰规,線程進(jìn)入synchronized保護(hù)的代碼之前獲得鎖汤功,
然后在正常執(zhí)行代碼完成后或者異常退出,都會(huì)自動(dòng)釋放鎖溜哮。
synchronized關(guān)鍵字在同步代碼塊中的應(yīng)用:
我們通過分析一下代碼的反匯編內(nèi)容來理解synchronized是如何利用monitor鎖來工作的
我們先來分析同步代碼塊的反匯編內(nèi)容
public class TestSync {
public void sync1(){
synchronized (this){
int ss = 10;
System.out.println(ss);
}
}
}
如上圖代碼滔金,我們定義的TestSync類中的sync1()方法中包含一個(gè)同步代碼塊,我們通過指令:javap -verbose TestSync.class
查看方法對(duì)應(yīng)的反匯編內(nèi)容如下:
public void sync1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter //加鎖
4: bipush 10
6: istore_2
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_2
11: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
14: aload_1
15: monitorexit //解鎖
16: goto 24
19: astore_3
20: aload_1
21: monitorexit 解鎖
22: aload_3
23: athrow
24: return
上述中我可以看出同步代碼塊實(shí)際上多了monitorenter和monitorexit指令茂嗓,
我們可以理解為對(duì)應(yīng)的加解鎖餐茵,之所以有一個(gè)monitorenter對(duì)應(yīng)兩個(gè)monitorexit是因?yàn)閖vm要
保證每個(gè)monitorenter必須有與之對(duì)應(yīng)的monitorexit,那么就需要在正常結(jié)束流程和異常結(jié)束流程中
分別執(zhí)行monitorexit以保證正呈鑫或者拋出異常情況下都能釋放鎖忿族。
monitorenter含義:
每個(gè)對(duì)象維護(hù)著一個(gè)計(jì)數(shù)器,沒有被鎖定的對(duì)象計(jì)數(shù)器為0蝌矛,執(zhí)行monitorenter的線程嘗試獲取monitor的所有權(quán)肠阱,那么會(huì)有以下三種情況:
如果該monitor的計(jì)數(shù)為0,則線程獲得該monitor后并將其計(jì)數(shù)設(shè)置成1朴读,然后該線程就該monitor的持有者。如果線程已經(jīng)獲取了該monitor走趋,那么該monitor的計(jì)數(shù)將累加衅金。
如果線程已經(jīng)是其他線程已經(jīng)獲取了該monitor,那么當(dāng)前想要獲取該monitor的線程會(huì)被阻塞簿煌,知道該monitor的計(jì)數(shù)為0的時(shí)候氮唯,代表該monitor已經(jīng)被釋放了,然后當(dāng)前線程就可以嘗試獲取該monitor了姨伟。
monitorexit含義:
monitorexit的作用是將monitor的計(jì)數(shù)減1惩琉,知道減為0為止,代表這個(gè)monitor已經(jīng)被釋放了夺荒,
那么此時(shí)其它線程就可以嘗試獲取該monitor的所有權(quán)了瞒渠。
synchronized關(guān)鍵字在同步方法中的應(yīng)用:
我們?cè)賮砜纯赐椒椒ǚ磪R編后的內(nèi)容又是怎么樣的良蒸,我們對(duì)一下內(nèi)容執(zhí)行反匯編。
public class TestSync {
public synchronized void sync2(){
int aa = 10;
System.out.println(aa);
}
}
反匯編代碼如下:
public synchronized void sync2();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=2, args_size=1
0: bipush 10
2: istore_1
3: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
6: iload_1
7: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
10: return
從上述代碼中我們可以看出同步方法和同步代碼塊的不同之處是同步代碼塊
中依賴monitorenter和monitorexit來加解鎖伍玖,同步方法中會(huì)多出一個(gè)
ACC_SYNCHRONIZED的flags修飾符嫩痰,來表明他是同步方法。所以被synchronized修飾的方法會(huì)有一
個(gè)ACC_SYNCHRONIZED標(biāo)志窍箍,那么當(dāng)線程要訪問某個(gè)方法的時(shí)候就會(huì)檢查方法是否攜帶ACC_SYNCHRONIZED標(biāo)志串纺,
帶的話就先去獲取monitor鎖,然后獲取到鎖后在執(zhí)行方法椰棘,方法執(zhí)行完后釋放monitor鎖纺棺。
synchronized關(guān)鍵字和Lock接口對(duì)比
相同點(diǎn):
- synchronized和Lock都是用來保護(hù)資源線程安全的。
- 都保證可見性邪狞,對(duì)于synchronized而言祷蝌,線程A進(jìn)入synchronized代碼塊或者方法中進(jìn)行的操作,對(duì)于后續(xù)的獲得同一個(gè)monitor鎖的線程B是可見的外恕。同理對(duì)于Lock而言杆逗,他和synchronized是一樣的都可以保證可見行
- synchronized和ReentrantLock都擁有可重入的特點(diǎn)
不同點(diǎn):
- 用法區(qū)別:synchronized關(guān)鍵字可以加在方法上不需要指定鎖對(duì)象,也可以新建一個(gè)同步代碼塊并且自定義monitor鎖對(duì)象鳞疲,而Lock接口必須顯示用Lock鎖對(duì)象開始加鎖lock()和解鎖unLock()罪郊,并且一般會(huì)在finally塊中確保用unLock()來解鎖以防止發(fā)生死鎖。
- 加解鎖順序不同尚洽,Lock可以不按照加鎖順序進(jìn)行解鎖比如我們先獲取A鎖悔橄,在獲取B鎖,那么解鎖時(shí)我們可以先解鎖A鎖在解鎖B鎖腺毫,但是synchronized的加解鎖必須有順序癣疟,比如獲取A鎖,在獲取B鎖潮酒,那么解鎖就是先解鎖B鎖睛挚,在解鎖A鎖。
- synchronized相比Lock不夠靈活急黎,synchronized鎖一旦被某個(gè)線程獲取了扎狱,那么其它線程只能阻塞等待釋放鎖,如果持有鎖的線程執(zhí)行很久那么整個(gè)程序的運(yùn)行效率就會(huì)很低勃教,而且如果一直不釋放鎖其他線程將一直等待下去淤击,相比Lock的lockInterruptibly方法,如果覺得持有鎖的線程執(zhí)行太久了可以中斷退出故源,還可以用tryLock()嘗試獲取鎖污抬,獲取不到就去執(zhí)行別的邏輯。
- Lock接口的一些實(shí)現(xiàn)類例如讀寫鎖中的讀鎖可以被多個(gè)線程持有绳军,synchronized只能被一個(gè)線程持有
- synchronized是內(nèi)置鎖(Monitor鎖)印机,有JVM實(shí)現(xiàn)加解鎖矢腻,還分為偏向鎖、輕量級(jí)鎖耳贬、重量級(jí)鎖踏堡,Lock接口根據(jù)實(shí)現(xiàn)不同有不同的底層原理。
- Lock可以設(shè)置是否公平鎖咒劲,synchronized不可以設(shè)置
- java6以后jdk對(duì)synchronized進(jìn)行了很多優(yōu)化顷蟆,所以synchronized性能并不比Lock差
公平鎖和非公平鎖
公平鎖和非公平鎖
公平鎖是指線程按照請(qǐng)求順序來分配鎖,非公平鎖是指不完全按照線程請(qǐng)求順序分配鎖腐魂,但是非公平鎖并不是完全的隨機(jī)分配而是”在合適的時(shí)機(jī)“插隊(duì)執(zhí)行帐偎。
什么是合適的時(shí)機(jī)
所謂合適的時(shí)機(jī)就是比如新來一個(gè)線程要獲取鎖,恰巧上一個(gè)持有鎖線程正好執(zhí)行完畢釋放了鎖蛔屹,那么此時(shí)這個(gè)新來的線程能不管后面排隊(duì)的線程而選擇立即插隊(duì)執(zhí)行削樊,但是如果上一個(gè)線程還未釋放鎖,那么新來的線程就需要去等待隊(duì)列排隊(duì)兔毒。
為什么設(shè)置非公平鎖
之所以設(shè)計(jì)非公平鎖是因?yàn)橄啾裙芥i排隊(duì)執(zhí)行漫贞,上一個(gè)線程釋放鎖后需要先喚醒下一個(gè)要執(zhí)行的線程,然后去獲取鎖在執(zhí)行育叁,而采用非公平鎖下迅脐,就可以上一個(gè)線程釋放了鎖,剛來一個(gè)新線程直接獲取鎖就插隊(duì)去執(zhí)行代碼了豪嗽,不需要額外的喚醒線程成本谴蔑,而且也有可能在線程喚醒的這段時(shí)間內(nèi),插隊(duì)線程已經(jīng)獲取鎖并且執(zhí)行完任務(wù)并釋放了鎖龟梦。
所以設(shè)置非公平鎖隐锭,這樣設(shè)計(jì)的原因是為了提高系統(tǒng)整體的運(yùn)行效率,而且ReentrantLock默認(rèn)的是非公平鎖计贰。
公平鎖和非公平鎖效果展示
公平鎖和非公平鎖通過設(shè)置ReentrantLock中boolean值來設(shè)置公平非公平鎖,如下代碼所示是設(shè)置為非公平鎖钦睡。
Lock lock=new ReentrantLock(false);
公平鎖代碼展示:
/**
* 描述:演示公平鎖,分別展示公平和不公平的情況躁倒,
* 非公平鎖會(huì)讓現(xiàn)在持有鎖的線程優(yōu)先再次獲取到鎖赎婚。
* 代碼借鑒自Java并發(fā)編程實(shí)戰(zhàn)手冊(cè)2.7
*/
public class FairAndNoFair {
public static void main(String[] args) {
PrintQueue printQueue = new PrintQueue();
Thread[] threads= new Thread[10];
for(int i=0;i<10;i++){
threads[i] = new Thread(new Job(printQueue),"Thread "+ i);
}
for (int i = 0; i < 10; i++) {
threads[i].start();
try {
Thread.sleep(100);//為了保證執(zhí)行順序
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Job implements Runnable{
private PrintQueue printQueue;
public Job(PrintQueue printQueue){
this.printQueue=printQueue;
}
@Override
public void run() {
System.out.printf("%s: Going to print a job\n", Thread.currentThread().getName());
printQueue.printJob();
System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName());
}
}
public class PrintQueue {
private final Lock lock=new ReentrantLock(false);
public void printJob(){
lock.lock();
try{
Long duration = (long) (Math。random()*10000);
System.out.printf("%s:First PrintQueue: Printing a Job during %d seconds\n",
Thread.currentThread().getName(), (duration / 1000));
Thread.sleep(duration);
} catch (InterruptedException e){
e.printStackTrace();
} finally {
lock.unlock();
}
lock.lock();
try{
Long duration = (long) (Math.random()*10000);
System.out.printf("%s:Second PrintQueue: Printing a Job during %d seconds\n",
Thread.currentThread().getName(), (duration / 1000));
Thread.sleep(duration);
} catch (InterruptedException e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
我們先運(yùn)行公平鎖的打印結(jié)果如下:
Thread 0: Going to print a job
Thread 0:First PrintQueue: Printing a Job during 9 seconds
Thread 1: Going to print a job //線程1-9進(jìn)入等待隊(duì)列排隊(duì)
Thread 2: Going to print a job
Thread 3: Going to print a job
Thread 4: Going to print a job
Thread 5: Going to print a job
Thread 6: Going to print a job
Thread 7: Going to print a job
Thread 8: Going to print a job
Thread 9: Going to print a job
Thread 1:First PrintQueue: Printing a Job during 5 seconds//線程0執(zhí)行完釋放了鎖樱溉,線程1開始執(zhí)行
Thread 2:First PrintQueue: Printing a Job during 1 seconds
Thread 3:First PrintQueue: Printing a Job during 9 seconds
Thread 4:First PrintQueue: Printing a Job during 7 seconds
Thread 5:First PrintQueue: Printing a Job during 8 seconds
Thread 6:First PrintQueue: Printing a Job during 5 seconds
Thread 7:First PrintQueue: Printing a Job during 2 seconds
Thread 8:First PrintQueue: Printing a Job during 9 seconds
Thread 9:First PrintQueue: Printing a Job during 7 seconds
Thread 0:Second PrintQueue: Printing a Job during 0 seconds
Thread 1:Second PrintQueue: Printing a Job during 6 seconds
Thread 0: The document has been printed
Thread 1: The document has been printed
Thread 2:Second PrintQueue: Printing a Job during 4 seconds
Thread 2: The document has been printed
Thread 3:Second PrintQueue: Printing a Job during 4 seconds
Thread 3: The document has been printed
Thread 4:Second PrintQueue: Printing a Job during 1 seconds
Thread 4: The document has been printed
Thread 5:Second PrintQueue: Printing a Job during 3 seconds
Thread 5: The document has been printed
Thread 6:Second PrintQueue: Printing a Job during 0 seconds
Thread 6: The document has been printed
Thread 7:Second PrintQueue: Printing a Job during 1 seconds
Thread 7: The document has been printed
Thread 8:Second PrintQueue: Printing a Job during 5 seconds
Thread 8: The document has been printed
Thread 9:Second PrintQueue: Printing a Job during 5 seconds
Thread 9: The document has been printed
Process finished with exit code 0
從上圖可以看出線程直接獲取鎖的順序是公平的,先到先得纬凤。
我們運(yùn)行非公平鎖的打印結(jié)果如下:
Thread 0: Going to print a job
Thread 0:First PrintQueue: Printing a Job during 5 seconds
Thread 1: Going to print a job
Thread 2: Going to print a job
Thread 3: Going to print a job
Thread 4: Going to print a job
Thread 5: Going to print a job
Thread 6: Going to print a job
Thread 7: Going to print a job
Thread 8: Going to print a job
Thread 9: Going to print a job
Thread 0:Second PrintQueue: Printing a Job during 2 seconds //線程0直接釋放鎖又獲取了鎖福贞,體現(xiàn)了非公平鎖
Thread 0: The document has been printed
Thread 1:First PrintQueue: Printing a Job during 9 seconds
Thread 1:Second PrintQueue: Printing a Job during 3 seconds
Thread 1: The document has been printed
Thread 2:First PrintQueue: Printing a Job during 0 seconds
Thread 3:First PrintQueue: Printing a Job during 0 seconds
Thread 3:Second PrintQueue: Printing a Job during 7 seconds
Thread 3: The document has been printed
Thread 4:First PrintQueue: Printing a Job during 3 seconds
Thread 4:Second PrintQueue: Printing a Job during 8 seconds
Thread 4: The document has been printed
Thread 5:First PrintQueue: Printing a Job during 6 seconds
Thread 5:Second PrintQueue: Printing a Job during 1 seconds
Thread 5: The document has been printed
Thread 6:First PrintQueue: Printing a Job during 0 seconds
Thread 6:Second PrintQueue: Printing a Job during 7 seconds
Thread 6: The document has been printed
Thread 7:First PrintQueue: Printing a Job during 8 seconds
Thread 7:Second PrintQueue: Printing a Job during 1 seconds
Thread 7: The document has been printed
Thread 8:First PrintQueue: Printing a Job during 9 seconds
Thread 8:Second PrintQueue: Printing a Job during 8 seconds
Thread 8: The document has been printed
Thread 9:First PrintQueue: Printing a Job during 5 seconds
Thread 9:Second PrintQueue: Printing a Job during 5 seconds
Thread 9: The document has been printed
Thread 2:Second PrintQueue: Printing a Job during 3 seconds
Thread 2: The document has been printed
Process finished with exit code 0
上圖中可以看出線程0在釋放了鎖之后,立刻有獲取了鎖繼續(xù)執(zhí)行停士,出現(xiàn)了搶鎖插隊(duì)現(xiàn)象(此時(shí)等待隊(duì)列已經(jīng)有了線程1-9再等待)挖帘。
公平鎖和非公平鎖有缺點(diǎn)
- 公平鎖優(yōu)勢(shì):公平鎖各個(gè)線程平等完丽,每個(gè)線程等待一段時(shí)間總會(huì)執(zhí)行。
- 公平鎖劣勢(shì):相對(duì)非公平鎖執(zhí)行效率比較慢拇舀,吞吐量更小
- 非公平鎖優(yōu)勢(shì):相比公平鎖更快逻族,吞吐量更大
- 非公平鎖劣勢(shì):又可能產(chǎn)生饑餓線程,就是某些線程的等待時(shí)間很長(zhǎng)始終得不到執(zhí)行骄崩。
公平鎖和非公平鎖源碼解析
首先公平鎖和非公平鎖都是繼承了ReentrantLock類中的內(nèi)部類Sync類聘鳞,這個(gè)Sync類繼承AQS(AbstractQueuedSynchronizer),Sync類代碼如下:
//源碼中可以看出Sync繼承了AbstractQueuedSynchronizer類
abstract static class Sync extends AbstractQueuedSynchronizer {...}
//Sync有公平鎖FairSync和非公平鎖NonFairSync兩個(gè)子類:
static final class NonfairSync extends Sync {要拂。抠璃。。}
static final class FairSync extends Sync {脱惰。扭粱。}
公平鎖獲取鎖的源碼:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//這里和非公平鎖對(duì)比多了個(gè)!hasQueuedPredecessors()判斷
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
非公平鎖獲取鎖源碼:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
通過對(duì)比可以發(fā)現(xiàn)锦聊,公平鎖和非公平鎖的區(qū)別主要是在獲取鎖的時(shí)候,
公平鎖多了一個(gè)hasQueuedPredecessors()為false的判斷,hasQueuedPredecessors()方法
就是判斷等待隊(duì)列中是否已經(jīng)有線程在等待了背伴,如果有則當(dāng)前線程不能在嘗試獲取鎖,對(duì)于非公平鎖而言训貌,
無論是否有線程在等待了既棺,都先嘗試獲取鎖,獲取不到的話再去排隊(duì)抽碌,tryLock()方法內(nèi)部調(diào)用的是sync.nonfairTryAcquire(1)即非
公平鎖悍赢,所以即使設(shè)置了公平模式,那么使用tryLock()也可以插隊(duì)货徙。
讀寫鎖
為什么設(shè)置讀寫鎖
首先讀寫鎖是為了提高系統(tǒng)的效率左权,
雖然普通的ReentrantLock可以保證線程安全,
但是如果是多個(gè)讀取操作痴颊,就直接采用ReentrantLock會(huì)大大的浪費(fèi)系統(tǒng)資源赏迟,
還有就是寫操作是不安全的,當(dāng)并發(fā)寫或者在進(jìn)行寫操作的同時(shí)進(jìn)行讀取蠢棱,都會(huì)發(fā)生線程安全問題锌杀,
那么設(shè)置的讀寫鎖就起了作用,讀寫鎖支持并發(fā)讀來提高讀的效率泻仙,同時(shí)又保證安全的寫操作糕再。
讀寫鎖規(guī)則
- 如果一個(gè)線程已經(jīng)占用了讀鎖,則此時(shí)其他線程如果要申請(qǐng)讀鎖玉转,可以申請(qǐng)成功突想。
- 如果一個(gè)線程已經(jīng)占用了讀鎖,則此時(shí)其他線程如果要申請(qǐng)寫鎖,則申請(qǐng)寫鎖的線程會(huì)一直等待釋放讀鎖猾担,因?yàn)樽x寫不能同時(shí)進(jìn)行袭灯。
- 如果一個(gè)線程已經(jīng)占用了寫鎖,則此時(shí)其他線程申請(qǐng)讀鎖或者寫鎖绑嘹,必須等待之前的線程釋放了寫鎖稽荧,因?yàn)樽x寫不能同時(shí)進(jìn)行。
讀寫鎖使用展示
這里使用ReentrantReadWriteLock來演示工腋,ReentrantReadWriteLock是ReadWriteLock的是實(shí)現(xiàn)類姨丈,最主要兩個(gè)方法readLock()獲取讀鎖,writeLock()獲取寫鎖夷蚊,
這里使用ReadWriteLock中的讀寫鎖進(jìn)行并發(fā)讀寫构挤,代碼展示如下:
public class ReadWriteLock {
//定義讀寫鎖
private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(false);
//獲取讀鎖
private static final ReentrantReadWriteLock.ReadLock readLock= reentrantReadWriteLock.readLock();
//獲取寫鎖
private static final ReentrantReadWriteLock.WriteLock writeLock= reentrantReadWriteLock.writeLock();
public static void read(){
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() +"得到讀鎖,正在讀取");
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
} finally {
System.out.println("釋放讀鎖");
readLock.unlock();
}
}
private static void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到寫鎖惕鼓,正在寫入");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "釋放寫鎖");
writeLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(() -> read()).start();
new Thread(() -> read()).start();
new Thread(() -> write()).start();
new Thread(() -> write()).start();
}
}
運(yùn)行結(jié)果如下:
Thread-0得到讀鎖筋现,正在讀取
Thread-1得到讀鎖,正在讀取
釋放讀鎖
釋放讀鎖
Thread-2得到寫鎖箱歧,正在寫入
Thread-2釋放寫鎖
Thread-3得到寫鎖矾飞,正在寫入
Thread-3釋放寫鎖
通過運(yùn)行結(jié)果可以看出,讀寫鎖支持并發(fā)讀呀邢,而寫操作是單獨(dú)進(jìn)行的洒沦。
讀鎖插隊(duì)策略
首先讀寫鎖ReentrantReadWriteLock支持公平鎖和非公平鎖,可以通過以下進(jìn)行設(shè)置:
//后面的boolean值用來設(shè)置公平鎖价淌、非公平鎖,其中的false設(shè)置為非公平鎖申眼,設(shè)置為true就是公平鎖,
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(false);
如果設(shè)置為公平鎖的時(shí)候?qū)?yīng)的讀寫鎖實(shí)現(xiàn)為:
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
其中的hasQueuedPredecessors()方法就是檢測(cè)等待隊(duì)列中是否已經(jīng)有線程在排序了蝉衣,
如果有的話每當(dāng)前獲取鎖的線程就會(huì)block去排序括尸,所以符合公平鎖定義。
如果設(shè)置為false非公平鎖病毡,則對(duì)應(yīng)的實(shí)現(xiàn)為:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation濒翻,
* block if the thread that momentarily appears to be head
* of queue, if one exists啦膜, is a waiting writer有送。 This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue。
*/
return apparentlyFirstQueuedIsExclusive();
}
}
上圖中writerShouldBlock()方法直接返回false僧家,可以看出對(duì)于想要獲取寫鎖的線程而言雀摘,
由于返回的是false所以它可以隨時(shí)插隊(duì),也符合非公平鎖的設(shè)計(jì)八拱,非公平鎖下的獲取讀鎖需要依據(jù)apparentlyFirstQueuedIsExclusive()方法的返回值阵赠,上圖中對(duì)apparentlyFirstQueuedIsExclusive方法注釋主要是說防止等待隊(duì)
列頭的寫線程無饑餓等待下去烁落,舉個(gè)例子說明:
場(chǎng)景:如果有線程1和2同時(shí)讀取,并且1和2已經(jīng)持有了讀鎖豌注,此時(shí)線程3想要寫入,所以線程3進(jìn)入等待隊(duì)列灯萍,此時(shí)線程4突然插隊(duì)想要獲取讀鎖轧铁。
此時(shí)就有兩種策略:
- 允許插隊(duì),允許線程4獲取讀鎖和線程1線程2一起去讀取旦棉,看似提高了讀取效率齿风,但是卻有一個(gè)嚴(yán)重的問題,就是如果后面來的線程一直都是想要獲取讀鎖的線程绑洛,那么線程3將一直得不到執(zhí)行的機(jī)會(huì)救斑,那么就會(huì)陷入“饑餓”狀態(tài),在長(zhǎng)時(shí)間內(nèi)得不到執(zhí)行真屯。
- 不允許插隊(duì)脸候,此時(shí)如果新來的線程4想要獲取讀鎖,必須去等排隊(duì)等待绑蔫,這種策略下运沦,線程3或優(yōu)先于線程4,就可以避免上面的“饑餓”狀態(tài)配深,直到線程3運(yùn)行結(jié)束携添,線程4才有機(jī)會(huì)運(yùn)行。
而ReentrantReadWriteLock非公平鎖下的獲取讀鎖正是采用了不允許插隊(duì)策略來實(shí)現(xiàn)的篓叶,避免了線程饑餓情況烈掠。
我們通過代碼展示一下上面的不允許插隊(duì)策略,效果展示代碼展示:
public class ReadWriteLock {
//定義讀寫鎖
private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(false);
//獲取讀鎖
private static final ReentrantReadWriteLock.ReadLock readLock= reentrantReadWriteLock.readLock();
//獲取寫鎖
private static final ReentrantReadWriteLock.WriteLock writeLock= reentrantReadWriteLock.writeLock();
public static void read(){
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() +"得到讀鎖缸托,正在讀取");
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
} finally {
System.out.println("釋放讀鎖");
readLock.unlock();
}
}
private static void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到寫鎖左敌,正在寫入");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "釋放寫鎖");
writeLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(() -> read()).start();
new Thread(() -> read()).start();
new Thread(() -> write()).start();
//以上代碼沒有改變,這里換成了讀鎖
new Thread(() -> read()).start();
}
}
運(yùn)行結(jié)果如下:
Thread-0得到讀鎖嗦董,正在讀取
Thread-1得到讀鎖母谎,正在讀取
釋放讀鎖
釋放讀鎖
Thread-2得到寫鎖,正在寫入
Thread-2釋放寫鎖
Thread-3得到讀鎖京革,正在讀取
釋放讀鎖
通過運(yùn)行結(jié)果我們可以看出奇唤,ReentrantReadWriteLock選擇了不允許插隊(duì)的策略。
讀寫鎖的升降級(jí)
寫鎖的降級(jí)
寫鎖的降級(jí)匹摇,代碼展示:
//定義讀寫鎖
private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
//獲取讀鎖
private static final ReentrantReadWriteLock.ReadLock readLock= reentrantReadWriteLock.readLock();
//獲取寫鎖
private static final ReentrantReadWriteLock.WriteLock writeLock= reentrantReadWriteLock.writeLock();
//鎖的降級(jí)
public static void downgrade(){
System.out.println(Thread.currentThread().getName()+"嘗試獲取寫鎖");
writeLock.lock();//獲取寫鎖
try {
System.out.println(Thread.currentThread().getName()+"獲取了寫鎖");
//在不釋放寫鎖的情況下直接獲取讀鎖咬扇,這就是讀寫鎖的降級(jí)
readLock.lock();
System.out.println(Thread.currentThread().getName()+"獲取了讀鎖");
}finally {
System.out.println(Thread.currentThread().getName()+"釋放了寫鎖");
//釋放了寫鎖,但是依然持有讀鎖廊勃,這里不釋放讀鎖懈贺,導(dǎo)致后面的線程無法獲取寫鎖
writeLock.unlock();
}
}
public static void main(String[] args) {
new Thread(() -> downgrade()).start();
new Thread(() -> downgrade()).start();
}
上圖運(yùn)行結(jié)果如下:
圖中我們可以看出線程0可以在持有寫鎖的情況下獲取到了讀鎖经窖,這就是寫鎖的降級(jí),因?yàn)榫€程0后面只是放了寫鎖梭灿,
并未釋放讀鎖画侣,導(dǎo)致后面的線程1不能獲取到寫鎖,所以程序一直阻塞堡妒。
讀鎖的升級(jí)
接下來我們?cè)趤砜醋x鎖的升級(jí)配乱,代碼展示:
private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(false);
//獲取讀鎖
private static final ReentrantReadWriteLock.ReadLock readLock= reentrantReadWriteLock.readLock();
//獲取寫鎖
private static final ReentrantReadWriteLock.WriteLock writeLock= reentrantReadWriteLock.writeLock();
//讀鎖升級(jí)
public static void upgarde(){
System.out.println(Thread.currentThread().getName()+"嘗試獲取讀鎖");
readLock.lock();
try{
System.out.println(Thread.currentThread().getName()+"獲取到了讀鎖");
System.out.println(Thread.currentThread().getName()+"阻塞獲取寫鎖");
//在持有讀鎖的情況下獲取寫鎖,此處會(huì)阻塞皮迟,表示不支持讀鎖升級(jí)到寫鎖
writeLock.lock();//此處會(huì)阻塞
System.out.println(Thread.currentThread().getName()+"獲取到了寫鎖");
}finally {
readLock.unlock();
}
}
public static void main(String[] args) {
new Thread(() -> upgarde()).start();
new Thread(() -> upgarde()).start();
}
運(yùn)行結(jié)果如下:
上圖中我們可以看出線程0和線程1都可以成功的獲取到讀鎖搬泥,但是在進(jìn)行鎖升級(jí)獲取寫鎖的時(shí)候都阻塞了,這是因?yàn)镽eentrantReadWriteLock 不支持讀鎖升級(jí)到寫鎖伏尼。
因?yàn)樽x鎖是可以多個(gè)線程持有的忿檩,但是寫鎖只能一個(gè)線程持有,并且不可能存在讀鎖和寫鎖同時(shí)持有的情況爆阶,也正是因?yàn)檫@個(gè)原因所以升級(jí)寫鎖的過程中燥透,需要等待所有的讀鎖都釋放了,此時(shí)才能進(jìn)行鎖升級(jí)扰她。
舉個(gè)例子兽掰,比如ABC三個(gè)線程都持有讀鎖,其中線程A想要進(jìn)行鎖升級(jí)徒役,必須要等到B和C都釋放了讀鎖孽尽,此時(shí)線程A才可以成功升級(jí)并獲取寫鎖。
但是這里也有一個(gè)問題忧勿,那就是假如A和B都想要鎖升級(jí)杉女,對(duì)于線程A來說,他需要等待其他所有線程包括B線程釋放讀鎖鸳吸,而B線程也需要等待其他線程釋放讀鎖包括A線程熏挎,那就會(huì)發(fā)生死鎖。
所以如果我們保證每次升級(jí)只有一個(gè)線程可以升級(jí)晌砾,那么就可以保證線程安全坎拐,并且實(shí)現(xiàn)。
自旋鎖
自旋鎖介紹
自旋鎖其實(shí)就是指循環(huán)养匈,比如while或者for循環(huán)哼勇,一直循環(huán)去嘗試獲取鎖,不像普通的鎖獲取不到就陷入阻塞呕乎。
自旋鎖和非自旋鎖流程圖對(duì)比如下:
上圖中我們可以看出自旋鎖獲取獲取鎖失敗并不會(huì)釋放CPU資源而是通過自旋的方式等待鎖的釋放积担,直到成功獲取到鎖為止。
而非自旋鎖如果嘗試獲取鎖失敗了猬仁,它就把自己的線程切換狀態(tài)帝璧,讓線程休眠先誉,釋放CPU時(shí)間片,然后直到之前持有這把鎖的線程釋放了鎖的烁,于是CPU再把之前的線程恢復(fù)回來褐耳,讓這個(gè)線程再嘗試去獲取鎖,如果再次失敗就在讓線程休眠渴庆,如果成功漱病,就可以獲取到同步資源的鎖。
自旋鎖的好處
自旋鎖免去了耗時(shí)的阻塞和喚醒線程操作把曼,避免了線程的狀態(tài)切換等開銷,提高了效率漓穿。
自旋鎖的壞處
自旋鎖雖然避免了線程切換的開銷嗤军,但是也帶來了新的開銷,因?yàn)樗枰煌5难h(huán)去嘗試獲取鎖晃危,如果所以只不釋放叙赚,那么他就需要一直去嘗試,這樣會(huì)白白的浪費(fèi)資源僚饭。
所以震叮,自旋鎖適用于并發(fā)度不是特別高,而且線程持有鎖的時(shí)間較短的場(chǎng)景鳍鸵。舉個(gè)例子比如java.util.concurrent包下的原子類大多數(shù)都是基于自旋鎖的實(shí)現(xiàn)苇瓣,比如AtomicInteger,我們查看他的getAndIncrement()方法偿乖,如下:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
很明顯击罪,do...while()循環(huán)就是一個(gè)自旋操作,如果在修改過程中遇到了其他線程導(dǎo)致沒有修改成功贪薪,則就會(huì)執(zhí)行循環(huán)不從的重試媳禁,直到修改成功為止。
如何自定義實(shí)現(xiàn)一個(gè)可重入的自旋鎖
實(shí)現(xiàn)代碼如下:
//自定義實(shí)現(xiàn)可重入的自旋鎖
public class CustomReentrantSpinLock {
private AtomicReference<Thread> owner=new AtomicReference<>();
private int count = 0;//重入次數(shù)
public void lock() {
Thread t = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"lock了");
if (t == owner.get()) {
++count;
return;
}
//自旋獲取鎖
while (!owner.compareAndSet(null, t)) {
System.out.println(Thread.currentThread().getName()+"自旋了");
}
}
public void unLock(){
Thread t=Thread.currentThread();
//只有當(dāng)前線程才能解鎖
if(t == owner.get()){
if(count >0){
--count;
} else {
owner.set(null);
}
}
}
public static void main(String[] args) {
CustomReentrantSpinLock spinLock=new CustomReentrantSpinLock();
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"開始嘗試獲取自旋鎖");
spinLock.lock();
try {
System.out.println(Thread.currentThread().getName()+"獲取到了自旋鎖");
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
} finally {
spinLock.unLock();
System.out.println(Thread.currentThread().getName()+"釋放了自旋鎖");
}
}
};
Thread thread1=new Thread(runnable);
Thread thread2=new Thread(runnable);
thread1.start();
thread2.start();
}
}
運(yùn)行結(jié)果如下:
Thread-0開始嘗試獲取自旋鎖
Thread-1開始嘗試獲取自旋鎖
Thread-0獲取到了自旋鎖
Thread-1自旋了
.
.
.
Thread-1自旋了
Thread-0釋放了了自旋鎖
Thread-1獲取到了自旋鎖
Thread-1釋放了了自旋鎖
從上圖運(yùn)行結(jié)果中可以看出画切,打印了很多Thread-1自旋了竣稽,說明自旋期間CPU依然不停運(yùn)轉(zhuǎn),Thread-1并沒有釋放CPU時(shí)間片霍弹。
JVM對(duì)鎖的優(yōu)化
從jdk1.6后HotSpot虛擬機(jī)對(duì)synchronized做了很多優(yōu)化毫别,包括自適應(yīng)自選、鎖消除庞萍、鎖粗化拧烦、偏向鎖、輕量級(jí)鎖等钝计,使得synchronized鎖得到了很大的性能提升恋博。
自適應(yīng)自旋鎖
自適應(yīng)自旋就是自旋的時(shí)間不在固定齐佳,而是根據(jù)自旋的成功率、失敗率债沮、以及當(dāng)前鎖的持有者的狀態(tài)等多種因素來共同決定的炼吴,就是說自旋的時(shí)間是變化的,這樣可以減少無用的自旋疫衩,提高效率硅蹦。
鎖消除
鎖消除是發(fā)生在編譯器級(jí)別的一種鎖優(yōu)化方式,有時(shí)候我們寫的代碼不需要加鎖闷煤,就比如加鎖的代碼實(shí)際上只有一個(gè)線程會(huì)執(zhí)行童芹,并不會(huì)出現(xiàn)多個(gè)線程并發(fā)訪問的情況,但是我們卻加上了synchronized鎖鲤拿,那么編譯器就可能會(huì)消除掉這個(gè)鎖假褪,比如下面StringBuffer的append操作:
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String。valueOf(obj));
return this;
}
代碼中我們可以看到這個(gè)方法被synchronized修飾近顷,因?yàn)樗赡軙?huì)被多個(gè)線程同時(shí)使用生音,
但是多數(shù)情況下它只會(huì)在一個(gè)線程內(nèi)使用,如果編譯器能確定這個(gè)對(duì)象只會(huì)在一個(gè)線程內(nèi)使用窒升,那么就表示肯定是線程安全的缀遍,編譯器就會(huì)做出優(yōu)化,把對(duì)應(yīng)的synchronized消除饱须,省去加解鎖的操作以提升效率域醇。
鎖粗化
鎖粗化主要是應(yīng)對(duì)剛釋放鎖,什么還沒做蓉媳,就重新獲取鎖歹苦,例如如下代碼:
public void lockCoarsening() {
synchronized (this) {
//do something
}
synchronized (this) {
//do something
}
synchronized (this) {
//do something
}
}
上述代碼中,當(dāng)線程在執(zhí)行第一個(gè)同步代碼塊時(shí)需要先獲取synchronized鎖督怜,然后執(zhí)行完了同步代碼塊在釋放synchronized鎖殴瘦,但是當(dāng)線程執(zhí)行完第一個(gè)同步代碼塊后已經(jīng)釋放了鎖后,緊接著線程立刻開始執(zhí)行第二個(gè)同步代碼塊時(shí)就需要對(duì)相同的鎖進(jìn)行獲取和釋放号杠,
這樣釋放和獲取鎖是完全沒有必要的蚪腋,如果把同步區(qū)域擴(kuò)大,也就是在最開始的時(shí)候加一次鎖姨蟋,在結(jié)束的時(shí)候釋放鎖屉凯,那么就可以把中間無意義的解鎖和加鎖的過程消除,相當(dāng)于把幾個(gè)synchronized塊合并成為一個(gè)較大的同步塊眼溶,好處就是無需頻繁的釋放鎖和獲取鎖悠砚,減少系統(tǒng)開銷。
但是鎖粗化不適用在循環(huán)的場(chǎng)景堂飞,僅適用非循環(huán)的場(chǎng)景灌旧,因?yàn)槿缦麓a所示绑咱,如果我們?cè)诘谝淮窝h(huán)中獲取鎖,在最后一次循環(huán)中釋放鎖枢泰,那么這就會(huì)導(dǎo)致其它線程長(zhǎng)時(shí)間無法獲取鎖描融。
for (int i = 0; i < 1000; i++) {
synchronized (this) {
//do something
}
}
鎖粗化默認(rèn)是開啟的,通過-XX:-EliminateLocks關(guān)閉該功能
偏向鎖衡蚂、輕量級(jí)鎖窿克、重量級(jí)鎖
這三種鎖我們最開始就介紹過,它們是指synchronized的鎖狀態(tài)的毛甲,通過在對(duì)象頭中的mark work字段來表明鎖的狀態(tài)年叮。
鎖升級(jí)的路徑
鎖升級(jí)的路徑如下圖所示,偏向鎖性能做好玻募,避免了CAS操作谋右,輕量級(jí)鎖利用了自旋和CAS操作避免了重量級(jí)鎖帶來的線程阻塞和喚醒,性能中等补箍,重量級(jí)鎖會(huì)把獲取不到鎖的線程阻塞,性能最差啸蜜。
-END
喜歡就掃描下方二維碼或微信搜索“程序員內(nèi)功心法”關(guān)注我吧
[圖片上傳失敗...(image-d203cc-1615787825391)]