詳盡探究synchronized原理后,原來性能這么好
前置思考
我們先來試著想一下實(shí)現(xiàn)一把鎖應(yīng)該考慮哪些問題
- 如何獲取鎖資源?
- 獲取不到鎖資源的線程如何處理?
- 如何釋放鎖資源?
- 資源釋放后如何讓其他線程再次獲取鎖資源?
帶著上面幾個(gè)問題更深層次的思考如何解決上面幾個(gè)問題
- 鎖的標(biāo)識(shí)
需要有個(gè)標(biāo)識(shí)或者狀態(tài)來表示鎖是否已經(jīng)被占用。 - 線程搶鎖的邏輯
多個(gè)線程如何搶鎖,如何才算搶到了鎖。 - 線程掛起的邏輯
線程如果沒有搶到鎖,我們都知道線程會(huì)阻塞掛起,那么如何阻塞掛起呢。 - 線程存儲(chǔ)機(jī)制
線程在Java中的表現(xiàn)其實(shí)就是一個(gè)Java對(duì)象綁定內(nèi)核中的一條線程,也可以理解為這個(gè)對(duì)象是底層線程的載體,沒有搶到鎖的線程會(huì)阻塞掛起,那么線程的載體如何處置呢。一般我們想到的是將這些載體保存到集合或者隊(duì)列中。 - 線程釋放鎖的邏輯
線程在執(zhí)行完業(yè)務(wù)邏輯后就要釋放鎖,如何才算釋放了鎖呢? - 線程喚醒的邏輯
鎖釋放后需要做什么呢,當(dāng)然是喚醒被阻塞的線程。
不管是哪一種鎖我認(rèn)為實(shí)現(xiàn)上必須都要考慮以上問題,而鎖的性能好壞就在于對(duì)以上問題解決的思路上是否為最優(yōu)的處理
synchronized的使用
在介紹原理前我們先介紹synchronized的使用
- synchronized可以修飾實(shí)例方法。
- synchronized可以修飾靜態(tài)方法。
- synchronized可以修飾實(shí)例方法的代碼塊。
- synchronized可以修飾靜態(tài)方法的代碼塊。
public void get(){
synchronized(this){
}
}
public synchronized void get(){
}
可見其使用是很簡(jiǎn)單的,只需要一個(gè)關(guān)鍵字,不需要程序員關(guān)心什么時(shí)候上鎖,什么時(shí)候釋放鎖。
對(duì)象的生成
創(chuàng)建對(duì)象
想要了解synchronized的底層原理就必須先了解Java中的對(duì)象是怎么生成的,這對(duì)于掌握synchronized原理至關(guān)重要。
java中如何創(chuàng)建一個(gè)對(duì)象?java代碼會(huì)被編譯成字節(jié)碼然后被jvm運(yùn)行,jvm在遇到new關(guān)鍵字的時(shí)候就會(huì)啟動(dòng)對(duì)象的創(chuàng)建流程,對(duì)象的大致流程如下:
圖片
默認(rèn)情況下jvm加載類是懶加載的,所以創(chuàng)建對(duì)象的第一步是判斷類是否已經(jīng)加載,如果沒有加載,需要先走類的加載流程。
接下來是分配內(nèi)存,一個(gè)對(duì)象在類加載的時(shí)候就可以知道所需要的內(nèi)存大小,此時(shí)就是在堆中劃分一塊區(qū)域出來作為對(duì)象的私密空間,具體如何分配和具體使用的垃圾回收器有關(guān),jvm篇再細(xì)講,在偌大的堆中怎么為一個(gè)對(duì)象劃分區(qū)間呢?這里的分配主要是兩種方法:指針碰撞和空閑列表,但是不管哪種劃分方法都會(huì)存在并發(fā)問題,此時(shí)jvm的解決方案是TLAB和cas配合失敗重試,這些內(nèi)容也將會(huì)在jvm篇進(jìn)行了解,這里只需要知道給對(duì)象分配了內(nèi)存即可。
初始化零值這一步是給對(duì)象中的屬性賦零值,比如int類型默認(rèn)為0,這一步是避免屬性不賦值的情況下出現(xiàn)空指針異常。
每個(gè)對(duì)象都會(huì)有一個(gè)對(duì)象頭區(qū)域,這個(gè)區(qū)域包括Mark Word,元數(shù)據(jù)指針,數(shù)組長(zhǎng)度三個(gè)部分,Mark Word用于保存對(duì)象的運(yùn)行時(shí)數(shù)據(jù),比如hashcode,分代年齡,鎖標(biāo)識(shí)等,元數(shù)據(jù)指針是當(dāng)前對(duì)象所屬類對(duì)象的地址,只有數(shù)組對(duì)象才會(huì)有數(shù)組長(zhǎng)度。
最后初始化對(duì)象,這個(gè)時(shí)候一個(gè)完整的對(duì)象生成了。
一個(gè)完整對(duì)象的結(jié)構(gòu)如下:
圖片
可以看到結(jié)構(gòu)中有一個(gè)對(duì)其填充,對(duì)其填充是為了滿足對(duì)象的大小為8字節(jié)的整數(shù)倍,只有8字節(jié)的整數(shù)倍才是最高效的存取方式。所以一個(gè)對(duì)象的大小總是8字節(jié)的整數(shù)倍。
對(duì)象頭
對(duì)象頭是對(duì)象中用于保存實(shí)例數(shù)據(jù)外的運(yùn)行時(shí)數(shù)據(jù)的區(qū)域。
我們知道java是面向?qū)ο蟮模趈ava的世界一切皆對(duì)象,所以整個(gè)jvm的設(shè)計(jì)都是圍繞對(duì)象,包括對(duì)象所屬類的加載,對(duì)象的創(chuàng)建,對(duì)象的保存,對(duì)象的銷毀,對(duì)象的回收,鎖的實(shí)現(xiàn),以及jvm的內(nèi)存結(jié)構(gòu)等等都要圍繞對(duì)象設(shè)計(jì),這就導(dǎo)致對(duì)象自身會(huì)有很多的運(yùn)行時(shí)數(shù)據(jù),比如垃圾回收依賴的分代年齡,代碼運(yùn)行過程中用于標(biāo)識(shí)對(duì)象唯一的hashcode,當(dāng)用作鎖對(duì)象的時(shí)候鎖的相關(guān)信息存儲(chǔ),記錄當(dāng)前對(duì)象所屬的類對(duì)象指針等等。
所以jvm設(shè)計(jì)了對(duì)象頭,對(duì)象頭包括Mark Word,MetaDate,數(shù)組長(zhǎng)度三部分。
Monitor
Java中的對(duì)象與生俱來會(huì)攜帶一個(gè)Monitor對(duì)象,這個(gè)Monitor對(duì)象可以說是對(duì)象的影子,它平時(shí)沒什么用,當(dāng)對(duì)象作為鎖資源被線程搶占的時(shí)候,它就排上用場(chǎng)了,可以說Monitor對(duì)象就是為實(shí)現(xiàn)鎖而發(fā)明的,Monitor就類似一個(gè)監(jiān)視器,所以說Java的老派鎖是監(jiān)視器鎖。
這個(gè)Monitor對(duì)象是jvm級(jí)別實(shí)現(xiàn)的,是一個(gè)jvm級(jí)別的對(duì)象,所以我們?cè)趈ava端開發(fā)的時(shí)候是看不到摸不到的,但卻是真實(shí)存在的。
Monitor對(duì)象結(jié)構(gòu)如下:
ObjectMonitor() {
_header = NULL;
_count = 0; // 記錄個(gè)數(shù)
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL; // 占用資源的線程
_WaitSet = NULL; // 處于wait狀態(tài)的線程,會(huì)被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; // 處于等待鎖block狀態(tài)的線程,會(huì)被加入到該列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
實(shí)現(xiàn)原理
synchronized鎖實(shí)現(xiàn)就是依賴這個(gè)Monitor對(duì)象實(shí)現(xiàn)的,我們看到上面Monitor對(duì)象的結(jié)構(gòu)中的幾個(gè)屬性:_count,_owner,_WaitSet,_EntryList,大概也能猜的出來的這些參數(shù)都跟線程搶鎖有關(guān)系。
好了,我們直接來看這個(gè)鎖的實(shí)現(xiàn)原理。
synchronized鎖的寫法很簡(jiǎn)單,簡(jiǎn)單到只有一個(gè)關(guān)鍵字,我們也知道Java代碼是要編譯成字節(jié)碼文件然后被jvm虛擬機(jī)解析運(yùn)行的,那就不難猜出這把鎖的邏輯實(shí)現(xiàn)是在編譯的字節(jié)碼中。
synchronized關(guān)鍵字被編譯后的大致表現(xiàn)如下:
圖片
整個(gè)代碼塊前后被monitorenter 和 monitorexit包裹,也就是說synchronized關(guān)鍵字的在編譯后就變?yōu)樯厦鎯蓚€(gè)關(guān)鍵字。到這里其實(shí)就和ReentrantLock的lock和unlock是一個(gè)道理了。
monitorenter 和 monitorexit這兩個(gè)是jvm級(jí)別字節(jié)碼指令,不難想到j(luò)vm在運(yùn)行代碼的時(shí)候,遇到monitorenter關(guān)鍵字,一定會(huì)啟動(dòng)搶鎖的邏輯,包括搶鎖,入隊(duì),阻塞;而遇到monitorexit的時(shí)候一定會(huì)走釋放鎖邏輯,包括釋放鎖,喚醒阻塞線程。
而所謂的入隊(duì)?wèi)?yīng)該就可以聯(lián)想到Monitor對(duì)象中的那幾個(gè)屬性了。阻塞的實(shí)現(xiàn)則是依賴于操作系統(tǒng)底層的互斥原語mutex。
上面做了一些相關(guān)知識(shí)的介紹,可能還比較碎片化,接下來我們就通過加鎖流程將所有信息都串聯(lián)起來。
Java1.5的鎖
java最開始的鎖的實(shí)現(xiàn)依賴鎖對(duì)象,對(duì)象的Monitor對(duì)象,操作系統(tǒng)互斥原語mutex。
- 線程運(yùn)行到 monitorenter 關(guān)鍵字后,jvm判斷此線程進(jìn)入了互斥區(qū)域,jvm底層會(huì)調(diào)用操作系統(tǒng)底層的互斥原語Mutex實(shí)現(xiàn)線程線程互斥。
- 如果在此之前沒有其他線程占據(jù)互斥區(qū)域,則當(dāng)前線程會(huì)占據(jù)互斥區(qū)域,意味著當(dāng)前線程搶到了鎖資源,會(huì)將自身的線程id存儲(chǔ)到鎖對(duì)象對(duì)應(yīng)的Monitor對(duì)象的_owner屬性,count屬性加1。
- 如果此時(shí)互斥區(qū)域已經(jīng)有一個(gè)線程占有,當(dāng)前線程會(huì)被阻塞,當(dāng)前線程id則會(huì)被存儲(chǔ)到鎖對(duì)象對(duì)應(yīng)的Monitor對(duì)象的_EntryList屬性,并且將線程阻塞掛起。
- 占有資源的線程繼續(xù)運(yùn)行,當(dāng)遇到monitorexit關(guān)鍵字的時(shí)候就要釋放鎖,此時(shí)jvm會(huì)喚醒隨機(jī)喚醒_EntryList里面的一個(gè)線程,被喚醒的線程會(huì)再次搶奪資源,沒有搶到資源的線程將會(huì)再次被阻塞,所以說synchronized是一個(gè)非公平的鎖。
那么鎖對(duì)象和Monitor對(duì)象是怎么關(guān)聯(lián)的呢?
是通過將Monitor對(duì)象的引用存儲(chǔ)到對(duì)象頭的Mark Word中實(shí)現(xiàn)的關(guān)聯(lián)。
Java1.6之前的synchronized大概就是這樣一個(gè)實(shí)現(xiàn)原理,之所以業(yè)界普遍認(rèn)為其性能很低是因?yàn)槠溆幸粋€(gè)很大的弊端,就是每個(gè)線程在搶鎖的時(shí)候都要調(diào)用操作系統(tǒng)的底層api,這就導(dǎo)致用戶態(tài)到內(nèi)核態(tài)的切換,我們都知道Java程序性能最忌諱的就是用戶態(tài)與內(nèi)核態(tài)的來回切換。然而,我們程序并不每時(shí)每刻都會(huì)有很多線程競(jìng)爭(zhēng)鎖資源,相反,大多數(shù)時(shí)間里,只有一個(gè)線程在執(zhí)行加鎖的邏輯,那么這種情況下每次都發(fā)生用戶態(tài)和內(nèi)核態(tài)切換無疑是沒有必要的性能消耗。所以業(yè)界對(duì)其性能持望而卻步的態(tài)度。
1.5后的鎖
JVM內(nèi)置鎖在1.5之后版本做了如下重大的優(yōu)化,在做了優(yōu)化后,其性能顯著提高,基本與ReentrantLock保持同等性能。
- 鎖粗化
- 鎖消除
- 鎖升級(jí):輕量級(jí)鎖 偏向鎖 適應(yīng)性自旋
接下來由淺入深講解優(yōu)化內(nèi)容
1. 鎖消除
鎖的消除,顧名思義就是將鎖去除,因?yàn)橛行﹫?chǎng)景下鎖是可以去除的
public void sync() {
Sync sync=new Sync();
synchronized(sync){
}
}
如上這種情況,我們知道進(jìn)出一個(gè)方法就是當(dāng)前線程棧的入棧出棧,所以方法內(nèi)部只要不涉及共享資源操作就是線程安全的,如上這段代碼,sync對(duì)象聲明在方法內(nèi)部,其引用是局部變量,是線程獨(dú)享資源不是共享資源,是線程獨(dú)有資源,隨著出棧發(fā)生,對(duì)象也就銷毀了,因此此處是可以不用加鎖的,鎖消除優(yōu)化就是對(duì)這種情況進(jìn)行去除鎖的處理。
jvm如何進(jìn)行優(yōu)化的呢?jvm在JIT編譯時(shí)(可以簡(jiǎn)單理解為當(dāng)某段代碼即將第一次被執(zhí)行時(shí)進(jìn)行編譯,又稱即時(shí)編譯),通過對(duì)運(yùn)行上下文的掃描,通過逃逸分析判斷方法中的是否存在共享資源,如果無共享資源則去除不存在共享資源競(jìng)爭(zhēng)的鎖,從而節(jié)省請(qǐng)求鎖時(shí)間。
典型的案例是StringBuffer的使用,后續(xù)會(huì)講解。
通過上面我們知道鎖消除依賴逃逸分析,逃逸分析是可以通過jvm參數(shù)配置的,如下:
-XX:+DoEscapeAnalysis 開啟逃逸分析
-XX:+EliminateLocks 開啟鎖消除
逃逸分析可以簡(jiǎn)單理解為分析資源是否能逃逸到其他方法或者其他線程中。
2. 鎖粗化
顧名思義,把小范圍的多個(gè)鎖變成大范圍少個(gè)鎖。
public Sync sync=new Sync();
public void sync() {
synchronized(sync){
}
synchronized(sync){
}
synchronized(sync){
}
}
上面的代碼可以看出,一個(gè)邏輯被多次加同一把鎖,每一次上鎖都是會(huì)耗時(shí)的,所以完全可以把多個(gè)鎖合并為一個(gè)鎖,這樣只需要上一次鎖就可以了,大大節(jié)省了時(shí)間。
同樣jvm在即時(shí)編譯的時(shí)候會(huì)掃描判斷是否存在可以粗化的鎖行為。
3. 鎖膨脹
鎖膨脹又叫鎖升級(jí)。synchronized之所以能在性能上與ReentrantLock持平就得益于鎖膨脹的優(yōu)化。
鎖升級(jí)是鎖優(yōu)化后的鎖機(jī)制,這個(gè)機(jī)制中包含這樣幾個(gè)概念:偏向鎖,輕量級(jí)鎖,適應(yīng)性自旋,重量級(jí)鎖。在整個(gè)鎖膨脹的過程中對(duì)對(duì)象頭的依賴更加明顯。
鎖升級(jí)是依靠對(duì)象頭的Mark Word來保存標(biāo)志信息的,接下來以32位操作系統(tǒng)來看下鎖升級(jí)過程中的對(duì)象頭中運(yùn)行時(shí)數(shù)據(jù)的變化。
圖片
- 無鎖狀態(tài) 當(dāng)沒有任何線程進(jìn)入的時(shí)候,此時(shí)處于無鎖狀態(tài),Mark Word中會(huì)有25bit的空間大小留給hashcode,4bit的空間大小留給對(duì)象的分代年齡信息,1bit的空間大小是標(biāo)識(shí)是否偏向(0否,1是),2bit的空間大小是鎖標(biāo)識(shí)位。
此時(shí)鎖標(biāo)志位為01,是否偏向?yàn)?,代表無鎖狀態(tài)。但是此時(shí)并不一定有hashcode,因?yàn)閔ashcode是代碼運(yùn)行過程中調(diào)用生成方法才生成的,如果運(yùn)行過程不調(diào)用就不會(huì)生成。
請(qǐng)注意,hashcode的生成是會(huì)影響鎖的升級(jí)過程的。
- 偏向鎖狀態(tài)
當(dāng)?shù)谝粋€(gè)線程T1進(jìn)入代碼塊后的步驟(前提條件是全程無hashcode生成)
- 判斷是否處于偏向中(通過Mark Word中的是否偏向判斷)
- 此時(shí)未處于偏向中,當(dāng)前線程會(huì)將自己的線程id保存到Mark Word中,設(shè)置是否偏向?yàn)?,此時(shí)鎖標(biāo)志位依然是01
此時(shí)Mark Word為:23bit的線程id,4bit的分代年齡,是否偏向?yàn)?,鎖標(biāo)識(shí)位依然為01。
此時(shí)是偏向鎖狀態(tài),它其實(shí)是一種特殊的無鎖狀態(tài)。
上面的過程是建立在全程無hashcode生成的基礎(chǔ)上,我們知道了hashcode會(huì)占用25bit,線程id會(huì)占用23bit,如果過程有hashcode生成怎么辦,這里涉及到兩個(gè)問題。
第一個(gè)問題,T1進(jìn)入前就已經(jīng)生成了hashcode怎么處理?
jvm的做法是如果偏向前已經(jīng)生成hashcode,那么就放棄偏向,直接進(jìn)入輕量級(jí)鎖。
第二個(gè)問題,T1進(jìn)入后鎖狀態(tài)變?yōu)榱似蜴i,此時(shí)生成hashcode怎么處理?
jvm的做法是撤銷偏向,直接進(jìn)入重量級(jí)鎖。
所以我們?cè)谑褂面i的時(shí)候要特別注意hashcode生成給鎖升級(jí)帶來的影響。
- 輕量級(jí)鎖狀態(tài)
當(dāng)?shù)诙€(gè)線程T2進(jìn)入代碼塊后
- 判斷是否處于偏向中(通過Mark Word中的是否偏向判斷)
- 如果處于偏向中,T2會(huì)以cas的方式試圖將Mark Word中的線程id替換為自己的線程id
- 如果T1已經(jīng)執(zhí)行完代碼塊,T2一定是可以替換成功的,此時(shí)鎖依然是偏向鎖狀態(tài)
- 如果T1沒有執(zhí)行完代碼塊,T2一定是替換不成功的,此時(shí)將進(jìn)入偏向鎖撤銷升級(jí)為輕量級(jí)鎖的過程
- 首先T1會(huì)進(jìn)入到安全點(diǎn),T1和T2會(huì)在自己的棧空間開辟一塊區(qū)域用于保存鎖記錄,同時(shí)復(fù)制一份Mark Word到這個(gè)鎖記錄中,同時(shí)cas的方式將自己??臻g這個(gè)鎖記錄的指針設(shè)置到Mark Word中去,因?yàn)門1持有偏向鎖,所以T1會(huì)優(yōu)先設(shè)置成功,此時(shí)Mark Word中有30bit的鎖記錄指針和2bit的鎖標(biāo)志位,此時(shí)的鎖標(biāo)志位為00代表輕量級(jí)鎖,鎖記錄指針指向當(dāng)前持有輕量級(jí)鎖的線程中??臻g的地址。T2沒有替換成功,將會(huì)進(jìn)入不斷輪詢失敗重試過程。
輕量級(jí)鎖是在資源競(jìng)爭(zhēng)壓力不是很大的情況下,避免每個(gè)線程都去獲取鎖而造成用戶態(tài)到內(nèi)核態(tài)的切換,這個(gè)切換是比較耗時(shí)的,這樣就能提高性能,但是如果競(jìng)爭(zhēng)壓力大的情況下輕量級(jí)鎖就不行了,因?yàn)閴毫Υ笠馕吨泻芏嗑€程在輪序失敗重試獲取輕量級(jí)鎖,短時(shí)間內(nèi)會(huì)造成cpu壓力飆升,甚至拖垮cpu,這個(gè)時(shí)候就必須升級(jí)為重量級(jí)鎖。
那么如何才算競(jìng)爭(zhēng)壓力大,什么時(shí)候會(huì)升級(jí)為重量級(jí)鎖呢?
jvm默認(rèn)輪詢次數(shù)限制值為十次,超過十次獲取不到資源就代表競(jìng)爭(zhēng)壓力比較大了,用戶也可以使用如下參數(shù)配置來自行更改這個(gè)次數(shù)
-XX:PreBlockSpin
但是有個(gè)問題,如果通過這個(gè)默認(rèn)值或者這個(gè)jvm參數(shù)配置限制數(shù)量,那意味著jvm全系統(tǒng)的鎖都要遵循,這個(gè)數(shù)字可能不適用于所有的鎖,因此jvm引入了自適應(yīng)的自旋。自適應(yīng)意味著自旋的時(shí)間不再是固定的了,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來決定的。如果在同一個(gè)鎖對(duì)象上,自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運(yùn)行中,那么虛擬機(jī)就會(huì)認(rèn)為這次自旋也很有可能再次成功,進(jìn)而允許自旋等待持續(xù)相對(duì)更長(zhǎng)的時(shí)間,比如持續(xù)100次忙循環(huán)。另一方面,如果對(duì)于某個(gè)鎖,自旋很少成功獲得過鎖,那在以后要獲取這個(gè)鎖時(shí)將有可能直接省略掉自旋過程,以避免浪費(fèi)處理器資源。有了自適應(yīng)自旋,隨著程序運(yùn)行時(shí)間的增長(zhǎng)及性能監(jiān)控信息的不斷完善,虛擬機(jī)對(duì)程序鎖的狀況預(yù)測(cè)就會(huì)越來越精準(zhǔn),虛擬機(jī)就會(huì)變得越來越“聰明”了。
- 重量級(jí)鎖
重量級(jí)鎖就是Monitor鎖,也叫監(jiān)視器鎖,其實(shí)現(xiàn)是依靠操作底層的互斥原語Mutes Lock,因?yàn)槊恳淮潍@取Monitor鎖都需要用戶態(tài)到內(nèi)核態(tài)的切換,所以比較耗時(shí),也是重量級(jí)鎖的由來。 當(dāng)自旋的條件破壞后,比如自旋次數(shù)達(dá)到限制或者競(jìng)爭(zhēng)的壓力越來越大,將不再自旋,輕量級(jí)鎖升級(jí)為重量級(jí)鎖,當(dāng)前對(duì)象頭中的Mark Word被復(fù)制一份到Monitor對(duì)象中,Mark Word中原來的輕量級(jí)鎖的鎖記錄指針被換成Monitor對(duì)象的指針,然后所有的線程會(huì)搶奪Monitor鎖的擁有權(quán),以cas方式將自己的線程id填充到Monitor對(duì)象的_owner字段,同時(shí)_count字段加1,當(dāng)然此時(shí)能夠cas成功的只會(huì)是原來持有輕量級(jí)鎖的線程,而那些沒有獲取到Monitor鎖的線程將會(huì)被阻塞并放入Monitor對(duì)象的_EntryList字段等待喚醒。
此時(shí)鎖的標(biāo)志位為10,表示重量級(jí)鎖。
當(dāng)線程退出Monitor鎖,便會(huì)將Monitor鎖中的_count減1,清空_owner,jvm會(huì)隨機(jī)喚醒_EntryList集合中一個(gè)線程重新獲取Monitor鎖,這個(gè)隨機(jī)便突出了synchronized的不公平性。
總結(jié)
在使用synchronized的時(shí)候一定要注意hashcode生成對(duì)鎖的影響,因?yàn)閷?duì)象頭的mark word是保存對(duì)象運(yùn)行期數(shù)據(jù)的,這塊區(qū)域在32位機(jī)器上是32字節(jié),在64位機(jī)器上是64字節(jié),所以空間是有限的,是無法同時(shí)保存hashcode和輕量級(jí)鎖記錄指針或者M(jìn)onitor對(duì)象指針的,因?yàn)闀?huì)做一些取舍。
synchronized鎖的隨機(jī)性決定了其非公平的特性。
synchronized鎖為什么是重量級(jí)鎖,為什么性能差,就是因?yàn)樵趦?yōu)化前,每個(gè)線程的進(jìn)入都會(huì)造成用戶態(tài)到內(nèi)核態(tài)的切換,而我們要的理想狀態(tài)是只有一個(gè)線程或者只有少量線程競(jìng)爭(zhēng)的時(shí)候不進(jìn)行用戶態(tài)到內(nèi)核態(tài)的切換。從而提高性能。優(yōu)化后做到了。