synchronized 鎖漫漫升級路
在圖文詳解Java對象內(nèi)存布局這篇文章中,在研究對象頭時我們了解了synchronized鎖升級的過程,由于篇幅有限,對鎖升級的過程介紹的比較簡略,本文在上一篇的基礎(chǔ)上,來詳細(xì)研究一下鎖升級的過程以及各個狀態(tài)下鎖的原理。本文結(jié)構(gòu)如下:
1 無鎖
2 偏向鎖
3 輕量級鎖
4 重量級鎖
總結(jié)
1 無鎖
在上一篇文章中,我們提到過 jvm會有4秒的偏向鎖開啟的延遲時間,在這個偏向延遲內(nèi)對象處于為無鎖態(tài)。如果關(guān)閉偏向鎖啟動延遲、或是經(jīng)過4秒且沒有線程競爭對象的鎖,那么對象會進(jìn)入無鎖可偏向狀態(tài)。
準(zhǔn)確來說,無鎖可偏向狀態(tài)應(yīng)該叫做匿名偏向(Anonymously biased)狀態(tài),因為這時對象的mark word中后三位已經(jīng)是101,但是threadId指針部分仍然全部為0,它還沒有向任何線程偏向。綜上所述,對象在剛被創(chuàng)建時,根據(jù)jvm的配置對象可能會處于 無鎖 或 匿名偏向 兩個狀態(tài)。
此外,如果在jvm的參數(shù)中關(guān)閉偏向鎖,那么直到有線程獲取這個鎖對象之前,會一直處于無鎖不可偏向狀態(tài)。修改jvm啟動參數(shù):
-XX:-UseBiasedLocking
延遲5s后打印對象內(nèi)存布局:
public?static?void?main(String[]?args)?throws?InterruptedException?{
????User?user=new?User();
????TimeUnit.SECONDS.sleep(5);
????System.out.println(ClassLayout.parseInstance(user).toPrintable());
}

可以看到,即使經(jīng)過一定的啟動延時,對象一直處于001無鎖不可偏向狀態(tài)。大家可能會有疑問,在無鎖狀態(tài)下,為什么要存在一個不可偏向狀態(tài)呢?通過查閱資料得到的解釋是:
JVM內(nèi)部的代碼有很多地方也用到了synchronized,明確在這些地方存在線程的競爭,如果還需要從偏向狀態(tài)再逐步升級,會帶來額外的性能損耗,所以JVM設(shè)置了一個偏向鎖的啟動延遲,來降低性能損耗
也就是說,在無鎖不可偏向狀態(tài)下,如果有線程試圖獲取鎖,那么將跳過升級偏向鎖的過程,直接使用輕量級鎖。使用代碼進(jìn)行驗證:
//-XX:-UseBiasedLocking
public?static?void?main(String[]?args)?throws?InterruptedException?{
????User?user=new?User();
????synchronized?(user){
????????System.out.println(ClassLayout.parseInstance(user).toPrintable());
????}
}
查看結(jié)果可以看到,在關(guān)閉偏向鎖情況下使用synchronized,鎖會直接升級為輕量級鎖(00狀態(tài)):

在目前的基礎(chǔ)上,可以用流程圖概括上面的過程:

額外注意一點就是匿名偏向狀態(tài)下,如果調(diào)用系統(tǒng)的hashCode()方法,會使對象回到無鎖態(tài),并在markword中寫入hashCode。并且在這個狀態(tài)下,如果有線程嘗試獲取鎖,會直接從無鎖升級到輕量級鎖,不會再升級為偏向鎖。
2 偏向鎖
2.1 偏向鎖原理
匿名偏向狀態(tài)是偏向鎖的初始狀態(tài),在這個狀態(tài)下第一個試圖獲取該對象的鎖的線程,會使用CAS操作(匯編命令CMPXCHG)嘗試將自己的threadID寫入對象頭的mark word中,使匿名偏向狀態(tài)升級為已偏向(Biased)的偏向鎖狀態(tài)。在已偏向狀態(tài)下,線程指針threadID非空,且偏向鎖的時間戳epoch為有效值。
如果之后有線程再次嘗試獲取鎖時,需要檢查mark word中存儲的threadID是否與自己相同即可,如果相同那么表示當(dāng)前線程已經(jīng)獲得了對象的鎖,不需要再使用CAS操作來進(jìn)行加鎖。
如果mark word中存儲的threadID與當(dāng)前線程不同,那么將執(zhí)行CAS操作,試圖將當(dāng)前線程的ID替換mark word中的threadID。只有當(dāng)對象處于下面兩種狀態(tài)中時,才可以執(zhí)行成功:
對象處于匿名偏向狀態(tài) 對象處于可重偏向(Rebiasable)狀態(tài),新線程可使用CAS將 threadID指向自己
如果對象不處于上面兩個狀態(tài),說明鎖存在線程競爭,在CAS替換失敗后會執(zhí)行偏向鎖撤銷操作。偏向鎖的撤銷需要等待全局安全點Safe Point(安全點是 jvm為了保證在垃圾回收的過程中引用關(guān)系不會發(fā)生變化設(shè)置的安全狀態(tài),在這個狀態(tài)上會暫停所有線程工作),在這個安全點會掛起獲得偏向鎖的線程。
在暫停線程后,會通過遍歷當(dāng)前jvm的所有線程的方式,檢查持有偏向鎖的線程狀態(tài)是否存活:
如果線程還存活,且線程正在執(zhí)行同步代碼塊中的代碼,則升級為輕量級鎖
如果持有偏向鎖的線程未存活,或者持有偏向鎖的線程未在執(zhí)行同步代碼塊中的代碼,則進(jìn)行校驗是否允許重偏向:
不允許重偏向,則撤銷偏向鎖,將
mark word升級為輕量級鎖,進(jìn)行CAS競爭鎖允許重偏向,設(shè)置為匿名偏向鎖狀態(tài),CAS將偏向鎖重新指向新線程
完成上面的操作后,喚醒暫停的線程,從安全點繼續(xù)執(zhí)行代碼。可以使用流程圖總結(jié)上面的過程:

2.2 偏向鎖升級
在上面的過程中,我們已經(jīng)知道了匿名偏向狀態(tài)可以變?yōu)闊o鎖態(tài)或升級為偏向鎖,接下來看一下偏向鎖的其他狀態(tài)的改變
偏向鎖升級為輕量級鎖
public?static?void?main(String[]?args)?throws?InterruptedException?{
????User?user=new?User();
????synchronized?(user){
????????System.out.println(ClassLayout.parseInstance(user).toPrintable());
????}
????Thread?thread?=?new?Thread(()?->?{
????????synchronized?(user)?{
????????????System.out.println("--THREAD--:"+ClassLayout.parseInstance(user).toPrintable());
????????}
????});
????thread.start();
????thread.join();
????System.out.println("--END--:"+ClassLayout.parseInstance(user).toPrintable());
}
查看內(nèi)存布局,偏向鎖升級為輕量級鎖,在執(zhí)行完成同步代碼后釋放鎖,變?yōu)闊o鎖不可偏向狀態(tài):

偏向鎖升級為重量級鎖
public?static?void?main(String[]?args)?throws?InterruptedException?{
????User?user=new?User();
????Thread?thread?=?new?Thread(()?->?{
????????synchronized?(user)?{
????????????System.out.println("--THREAD1--:"?+?ClassLayout.parseInstance(user).toPrintable());
????????????try?{
????????????????user.wait(2000);
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????????System.out.println("--THREAD?END--:"?+?ClassLayout.parseInstance(user).toPrintable());
????????}
????});
????thread.start();
????thread.join();
????TimeUnit.SECONDS.sleep(3);
????System.out.println(ClassLayout.parseInstance(user).toPrintable());
}
查看內(nèi)存布局,可以看到在調(diào)用了對象的wait()方法后,直接從偏向鎖升級成了重量級鎖,并在鎖釋放后變?yōu)闊o鎖態(tài):

這里是因為wait()方法調(diào)用過程中依賴于重量級鎖中與對象關(guān)聯(lián)的monitor,在調(diào)用wait()方法后monitor會把線程變?yōu)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">WAITING狀態(tài),所以才會強(qiáng)制升級為重量級鎖。除此之外,調(diào)用hashCode方法時也會使偏向鎖直接升級為重量級鎖。
在上面分析的基礎(chǔ)上,再加上我們上一篇中講到的輕量級鎖升級到重量級鎖的知識,就可以對上面的流程圖進(jìn)行完善了:

2.3 批量重偏向
在未禁用偏向鎖的情況下,當(dāng)一個線程建立了大量對象,并且對它們執(zhí)行完同步操作解鎖后,所有對象處于偏向鎖狀態(tài),此時若再來另一個線程也嘗試獲取這些對象的鎖,就會導(dǎo)偏向鎖的批量重偏向(Bulk Rebias)。當(dāng)觸發(fā)批量重偏向后,第一個線程結(jié)束同步操作后的鎖對象當(dāng)再被同步訪問時會被重置為可重偏向狀態(tài),以便允許快速重偏向,這樣能夠減少撤銷偏向鎖再升級為輕量級鎖的性能消耗。
首先看一下和偏向鎖有關(guān)的參數(shù),修改jvm啟動參數(shù),使用下面的命令可以在項目啟動時打印jvm的默認(rèn)參數(shù)值:
-XX:+PrintFlagsFinal
需要關(guān)注的屬性有下面3個:

BiasedLockingBulkRebiasThreshold:偏向鎖批量重偏向閾值,默認(rèn)為20次BiasedLockingBulkRevokeThreshold:偏向鎖批量撤銷閾值,默認(rèn)為40次BiasedLockingDecayTime:重置計數(shù)的延遲時間,默認(rèn)值為25000毫秒(即25秒)
批量重偏向是以class而不是對象為單位的,每個class會維護(hù)一個偏向鎖的撤銷計數(shù)器,每當(dāng)該class的對象發(fā)生偏向鎖的撤銷時,該計數(shù)器會加一,當(dāng)這個值達(dá)到默認(rèn)閾值20時,jvm就會認(rèn)為這個鎖對象不再適合原線程,因此進(jìn)行批量重偏向。而距離上次批量重偏向的25秒內(nèi),如果撤銷計數(shù)達(dá)到40,就會發(fā)生批量撤銷,如果超過25秒,那么就會重置在[20, 40)內(nèi)的計數(shù)。
上面這段理論是不是聽上去有些難理解,沒關(guān)系,我們先用代碼驗證批量重偏向的過程:
private?static?Thread?t1,t2;
public?static?void?main(String[]?args)?throws?InterruptedException?{??????
????TimeUnit.SECONDS.sleep(5);
????List分析上面的代碼,當(dāng)線程t1運(yùn)行結(jié)束后,數(shù)組中所有對象的鎖都偏向t1,然后t1喚醒被掛起的線程t2,線程t2嘗試獲取前30個對象的鎖。我們打印線程t2獲取到的第19和第20個對象的鎖狀態(tài):

線程t2在訪問前19個對象時對象的偏向鎖會升級到輕量級鎖,在訪問后11個對象(下標(biāo)19-29)時,因為偏向鎖撤銷次數(shù)達(dá)到了20,會觸發(fā)批量重偏向,將鎖的狀態(tài)變?yōu)槠蚓€程t2。在全部線程結(jié)束后,再次查看第19、20、30、31個對象鎖的狀態(tài):

線程t2結(jié)束后,第1-19的對象釋放輕量級鎖變?yōu)闊o鎖不可偏向狀態(tài),第20-30的對象狀態(tài)為偏向鎖、但從偏向t1改為偏向t2,第31-40的對象因為沒有被線程t2訪問所以保持偏向線程t1不變。
2.4 批量撤銷
在多線程競爭激烈的狀況下,使用偏向鎖將會導(dǎo)致性能降低,因此產(chǎn)生了批量撤銷機(jī)制,接下來使用代碼進(jìn)行測試:
private?static?Thread?t1,?t2,?t3;
public?static?void?main(String[]?args)?throws?InterruptedException?{
????TimeUnit.SECONDS.sleep(5);
????List對上面的運(yùn)行流程進(jìn)行分析:
線程 t1中,第1-40的鎖對象狀態(tài)變?yōu)槠蜴i線程 t2中,第1-19的鎖對象撤銷偏向鎖升級為輕量級鎖,然后對第20-40的對象進(jìn)行批量重偏向線程 t3中,首先直接對第1-19個對象競爭輕量級鎖,而從第20個對象開始往后的對象不會再次進(jìn)行批量重偏向,因此第20-39的對象進(jìn)行偏向鎖撤銷升級為輕量級鎖,這時t2和t3線程一共執(zhí)行了40次的鎖撤銷,觸發(fā)鎖的批量撤銷機(jī)制,對偏向鎖進(jìn)行撤銷置為輕量級鎖
看一下在3個線程都結(jié)束后創(chuàng)建的新對象:

可以看到,創(chuàng)建的新對象為無鎖不可偏向狀態(tài)001,說明當(dāng)類觸發(fā)了批量撤銷機(jī)制后,jvm會禁用該類創(chuàng)建對象時的可偏向性,該類新創(chuàng)建的對象全部為無鎖不可偏向狀態(tài)。
2.5 總結(jié)
偏向鎖通過消除資源無競爭情況下的同步原語,提高了程序在單線程下訪問同步資源的運(yùn)行性能,但是當(dāng)出現(xiàn)多個線程競爭時,就會撤銷偏向鎖、升級為輕量級鎖。
如果我們的應(yīng)用系統(tǒng)是高并發(fā)、并且代碼中同步資源一直是被多線程訪問的,那么撤銷偏向鎖這一步就顯得多余,偏向鎖撤銷時進(jìn)入Safe Point產(chǎn)生STW的現(xiàn)象應(yīng)該是被極力避免的,這時應(yīng)該通過禁用偏向鎖來減少性能上的損耗。
3 輕量級鎖
3.1 輕量級鎖原理
1、在代碼訪問同步資源時,如果鎖對象處于無鎖不可偏向狀態(tài),jvm首先將在當(dāng)前線程的棧幀中創(chuàng)建一條鎖記錄(lock record),用于存放:
displaced mark word(置換標(biāo)記字):存放鎖對象當(dāng)前的mark word的拷貝owner指針:指向當(dāng)前的鎖對象的指針,在拷貝mark word階段暫時不會處理它

2、在拷貝mark word完成后,首先會掛起線程,jvm使用CAS操作嘗試將對象的 mark word 中的 lock record 指針指向棧幀中的鎖記錄,并將鎖記錄中的owner指針指向鎖對象的mark word
如果CAS替換成功,表示競爭鎖對象成功,則將鎖標(biāo)志位設(shè)置成 00,表示對象處于輕量級鎖狀態(tài),執(zhí)行同步代碼中的操作

如果CAS替換失敗,則判斷當(dāng)前對象的 mark word是否指向當(dāng)前線程的棧幀:如果是則表示當(dāng)前線程已經(jīng)持有對象的鎖,執(zhí)行的是 synchronized的鎖重入過程,可以直接執(zhí)行同步代碼塊否則說明該其他線程已經(jīng)持有了該對象的鎖,如果在自旋一定次數(shù)后仍未獲得鎖,那么輕量級鎖需要升級為重量級鎖,將鎖標(biāo)志位變成 10,后面等待的線程將會進(jìn)入阻塞狀態(tài)
4、輕量級鎖的釋放同樣使用了CAS操作,嘗試將displaced mark word 替換回mark word,這時需要檢查鎖對象的mark word中lock record指針是否指向當(dāng)前線程的鎖記錄:
如果替換成功,則表示沒有競爭發(fā)生,整個同步過程就完成了 如果替換失敗,則表示當(dāng)前鎖資源存在競爭,有可能其他線程在這段時間里嘗試過獲取鎖失敗,導(dǎo)致自身被掛起,并修改了鎖對象的 mark word升級為重量級鎖,最后在執(zhí)行重量級鎖的解鎖流程后喚醒被掛起的線程
用流程圖對上面的過程進(jìn)行描述:

3.2 輕量級鎖重入
我們知道,synchronized是可以鎖重入的,在輕量級鎖的情況下重入也是依賴于棧上的lock record完成的。以下面的代碼中3次鎖重入為例:
synchronized?(user){
????synchronized?(user){
????????synchronized?(user){
????????????//TODO
????????}
????}
}
輕量級鎖的每次重入,都會在棧中生成一個lock record,但是保存的數(shù)據(jù)不同:
首次分配的 lock record,displaced mark word復(fù)制了鎖對象的mark word,owner指針指向鎖對象之后重入時在棧中分配的 lock record中的displaced mark word為null,只存儲了指向?qū)ο蟮?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">owner指針

輕量級鎖中,重入的次數(shù)等于該鎖對象在棧幀中lock record的數(shù)量,這個數(shù)量隱式地充當(dāng)了鎖重入機(jī)制的計數(shù)器。這里需要計數(shù)的原因是每次解鎖都需要對應(yīng)一次加鎖,只有最后解鎖次數(shù)等于加鎖次數(shù)時,鎖對象才會被真正釋放。在釋放鎖的過程中,如果是重入則刪除棧中的lock record,直到?jīng)]有重入時則使用CAS替換鎖對象的mark word。
3.3 輕量級鎖升級
在jdk1.6以前,默認(rèn)輕量級鎖自旋次數(shù)是10次,如果超過這個次數(shù)或自旋線程數(shù)超過CPU核數(shù)的一半,就會升級為重量級鎖。這時因為如果自旋次數(shù)過多,或過多線程進(jìn)入自旋,會導(dǎo)致消耗過多cpu資源,重量級鎖情況下線程進(jìn)入等待隊列可以降低cpu資源的消耗。自旋次數(shù)的值也可以通過jvm參數(shù)進(jìn)行修改:
-XX:PreBlockSpin
jdk1.6以后加入了自適應(yīng)自旋鎖 (Adapative Self Spinning),自旋的次數(shù)不再固定,由jvm自己控制,由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)來決定:
對于某個鎖對象,如果自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運(yùn)行中,那么虛擬機(jī)就會認(rèn)為這次自旋也是很有可能再次成功,進(jìn)而允許自旋等待持續(xù)相對更長時間 對于某個鎖對象,如果自旋很少成功獲得過鎖,那在以后嘗試獲取這個鎖時將可能省略掉自旋過程,直接阻塞線程,避免浪費(fèi)處理器資源。
下面通過代碼驗證輕量級鎖升級為重量級鎖的過程:
public?static?void?main(String[]?args)?throws?InterruptedException?{
????User?user?=?new?User();
????System.out.println("--MAIN--:"?+?ClassLayout.parseInstance(user).toPrintable());
????Thread?thread1?=?new?Thread(()?->?{
????????synchronized?(user)?{
????????????System.out.println("--THREAD1--:"?+?ClassLayout.parseInstance(user).toPrintable());
????????????try?{
????????????????TimeUnit.SECONDS.sleep(5);
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????}
????});
????Thread?thread2?=?new?Thread(()?->?{
????????try?{
????????????TimeUnit.SECONDS.sleep(2);
????????}?catch?(InterruptedException?e)?{
????????????e.printStackTrace();
????????}
????????synchronized?(user)?{
????????????System.out.println("--THREAD2--:"?+?ClassLayout.parseInstance(user).toPrintable());
????????}
????});
????thread1.start();
????thread2.start();
????thread1.join();
????thread2.join();
????TimeUnit.SECONDS.sleep(3);
????System.out.println(ClassLayout.parseInstance(user).toPrintable());
}
在上面的代碼中,線程2在啟動后休眠兩秒后再嘗試獲取鎖,確保線程1能夠先得到鎖,在此基礎(chǔ)上造成鎖對象的資源競爭。查看對象鎖狀態(tài)變化:

在線程1持有輕量級鎖的情況下,線程2嘗試獲取鎖,導(dǎo)致資源競爭,使輕量級鎖升級到重量級鎖。在兩個線程都運(yùn)行結(jié)束后,可以看到對象的狀態(tài)恢復(fù)為了無鎖不可偏向狀態(tài),在下一次線程嘗試獲取鎖時,會直接從輕量級鎖狀態(tài)開始。
上面在最后一次打印前將主線程休眠3秒的原因是鎖的釋放過程需要一定的時間,如果在線程執(zhí)行完成后直接打印對象內(nèi)存布局,對象可能仍處于重量級鎖狀態(tài)。
3.4 總結(jié)
輕量級鎖與偏向鎖類似,都是jdk對于多線程的優(yōu)化,不同的是輕量級鎖是通過CAS來避免開銷較大的互斥操作,而偏向鎖是在無資源競爭的情況下完全消除同步。
輕量級鎖的“輕量”是相對于重量級鎖而言的,它的性能會稍好一些。輕量級鎖嘗試?yán)肅AS,在升級為重量級鎖之前進(jìn)行補(bǔ)救,目的是為了減少多線程進(jìn)入互斥,當(dāng)多個線程交替執(zhí)行同步塊時,jvm使用輕量級鎖來保證同步,避免線程切換的開銷,不會造成用戶態(tài)與內(nèi)核態(tài)的切換。但是如果過度自旋,會引起cpu資源的浪費(fèi),這種情況下輕量級鎖消耗的資源可能反而會更多。
4 重量級鎖
4.1 Monitor
重量級鎖是依賴對象內(nèi)部的monitor(監(jiān)視器/管程)來實現(xiàn)的 ,而monitor 又依賴于操作系統(tǒng)底層的Mutex Lock(互斥鎖)實現(xiàn),這也就是為什么說重量級鎖比較“重”的原因了,操作系統(tǒng)在實現(xiàn)線程之間的切換時,需要從用戶態(tài)切換到內(nèi)核態(tài),成本非常高。在學(xué)習(xí)重量級鎖的工作原理前,首先需要了解一下monitor中的核心概念:
owner:標(biāo)識擁有該monitor的線程,初始時和鎖被釋放后都為nullcxq (ConnectionList):競爭隊列,所有競爭鎖的線程都會首先被放入這個隊列中EntryList:候選者列表,當(dāng)owner解鎖時會將cxq隊列中的線程移動到該隊列中OnDeck:在將線程從cxq移動到EntryList時,會指定某個線程為Ready狀態(tài)(即OnDeck),表明它可以競爭鎖,如果競爭成功那么稱為owner線程,如果失敗則放回EntryList中WaitSet:因為調(diào)用wait()或wait(time)方法而被阻塞的線程會被放在該隊列中count:monitor的計數(shù)器,數(shù)值加1表示當(dāng)前對象的鎖被一個線程獲取,線程釋放monitor對象時減1recursions:線程重入次數(shù)
用圖來表示線程競爭的的過程:

當(dāng)線程調(diào)用wait()方法,將釋放當(dāng)前持有的monitor,將owner置為null,進(jìn)入WaitSet集合中等待被喚醒。當(dāng)有線程調(diào)用notify()或notifyAll()方法時,也會釋放持有的monitor,并喚醒WaitSet的線程重新參與monitor的競爭。
4.2 重量級鎖原理
當(dāng)升級為重量級鎖的情況下,鎖對象的mark word中的指針不再指向線程棧中的lock record,而是指向堆中與鎖對象關(guān)聯(lián)的monitor對象。當(dāng)多個線程同時訪問同步代碼時,這些線程會先嘗試獲取當(dāng)前鎖對象對應(yīng)的monitor的所有權(quán):
獲取成功,判斷當(dāng)前線程是不是重入,如果是重入那么 recursions+1獲取失敗,當(dāng)前線程會被阻塞,等待其他線程解鎖后被喚醒,再次競爭鎖對象
在重量級鎖的情況下,加解鎖的過程涉及到操作系統(tǒng)的Mutex Lock進(jìn)行互斥操作,線程間的調(diào)度和線程的狀態(tài)變更過程需要在用戶態(tài)和核心態(tài)之間進(jìn)行切換,會導(dǎo)致消耗大量的cpu資源,導(dǎo)致性能降低。
總結(jié)
在 jdk1.6 中,引入了偏向鎖和輕量級鎖,并使用鎖升級機(jī)制對synchronized進(jìn)行了充分的優(yōu)化。其實除鎖升級外,還使用了鎖消除、鎖粗化等優(yōu)化手段,所以對它的認(rèn)識要脫離“重量級”這一概念,不要再單純的認(rèn)為它的性能差了。在某些場景下,synchronized的性能甚至已經(jīng)超過了Lock同步鎖。
盡管java對synchronized做了這些優(yōu)化,但是在使用過程中,我們還是要盡量減少鎖的競爭,通過減小加鎖粒度和減少同步代碼的執(zhí)行時間,來降低鎖競爭,盡量使鎖維持在偏向鎖和輕量級鎖的級別,避免升級為重量級鎖,造成性能的損耗。
最后不得不再提一句,在java15中已經(jīng)默認(rèn)禁用了偏向鎖,并棄用所有相關(guān)的命令行選項,雖然說不確定未來的LTS版本會怎樣改動,但是了解一下偏向鎖的基礎(chǔ)也沒什么不好的,畢竟你發(fā)任你發(fā),我用java8~
公眾號后臺回復(fù)
"面試"---領(lǐng)取大廠面試資料
"導(dǎo)圖"---領(lǐng)取24張Java后端學(xué)習(xí)筆記導(dǎo)圖
"架構(gòu)"---領(lǐng)取29本java架構(gòu)師電子書籍
"實戰(zhàn)"---領(lǐng)取springboot實戰(zhàn)項目
"視頻"---領(lǐng)取最新java架構(gòu)師視頻
"加群"---加入學(xué)習(xí)交流群
掃碼關(guān)注公眾號
有趣、深入、直接
與你聊聊技術(shù)
覺得有用,一鍵四連吧~
