作者:小傅哥 博客:https://
沉淀、分享、成長,讓自己和他人都能有所收獲!😄
一、前言
感覺什么都不會(huì),從哪開始呀!
這是最近我總能被問到的問題,也確實(shí)是。一個(gè)初入編程職場的新人,或是一個(gè)想重新努力學(xué)習(xí)的老司機(jī),這也不會(huì),那也不會(huì),總會(huì)犯愁從哪開始。
講道理,畢竟 Java 涉及的知識太多了,要學(xué)應(yīng)該是學(xué)會(huì)學(xué)習(xí)的能力,而不是去背題、背答案,拾人牙慧是不會(huì)有太多收益的。
學(xué)習(xí)的過程要找對方法,遇到問題時(shí)最好能自己想想,你有哪些方式學(xué)會(huì)這些知識。是不感覺即使讓你去百度搜,你都不知道應(yīng)該拿哪個(gè)關(guān)鍵字搜!只能拿著問題直接找人問,這樣缺少思考,缺少大腦撞南墻的過程,其實(shí)最后也很難學(xué)會(huì)。
所以,你要學(xué)會(huì)的是自我學(xué)習(xí)的能力,之后是從哪開始都可以,重要的是開始和堅(jiān)持!
二、面試題
謝飛機(jī),小記
,周末逛完奧特萊斯,回來就跑面試官家去了!
謝飛機(jī) :duang、duang、duang,我來了!
面試官 :來的還挺準(zhǔn)時(shí),洗洗手吃飯吧!
謝飛機(jī) :嘿嘿…
面試官 :你看我這塊魚豆腐,像不像 synchronized 鎖!
謝飛機(jī) :啊!?
面試官 :飛機(jī),正好問你。synchronized、volatile,有什么區(qū)別呀?
謝飛機(jī) :嗯,volatile 保證可見性,synchronized 保證原子性!
面試官 :那不用 volatile,只用 synchronized 修飾方式,能保證可見性嗎?
謝飛機(jī) :這…,我沒驗(yàn)證過!
面試官 :吃吧,吃吧!一會(huì)給你個(gè) synchronized 學(xué)習(xí)大綱,照著整理知識點(diǎn)!
三、synchronized 解毒
1. 對象結(jié)構(gòu)
1.1 對象結(jié)構(gòu)介紹
HotSpot虛擬機(jī) markOop.cpp 中的 C++ 代碼注釋片段,描述了 64bits 下 mark-word 的存儲(chǔ)狀態(tài),也就是圖 15-1 的結(jié)構(gòu)示意。
這部分的源碼注釋如下:
64 bits:
-- -- -- --
unused: 25 hash: 31 -- > | unused: 1 age: 4 biased_lock: 1 lock: 2 ( normal object)
JavaThread* : 54 epoch: 2 unused: 1 age: 4 biased_lock: 1 lock: 2 ( biased object)
PromotedObject* : 61 -- -- -- -- -- -- -- -- -- -- - > | promo_bits: 3 -- -- - > | ( CMS promoted object)
size: 64 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > | ( CMS free block)
unused: 25 hash: 31 -- > | cms_free: 1 age: 4 biased_lock: 1 lock: 2 ( COOPs && normal object)
JavaThread* : 54 epoch: 2 cms_free: 1 age: 4 biased_lock: 1 lock: 2 ( COOPs && biased object)
narrowOop: 32 unused: 24 cms_free: 1 unused: 4 promo_bits: 3 -- -- - > | ( COOPs && CMS promoted object)
unused: 21 size: 35 -- > | cms_free: 1 unused: 7 -- -- -- -- -- -- -- -- -- > | ( COOPs && CMS free block)
源碼地址 :jdk8/hotspot/file/vm/oops/markOop.hpp
HotSpot虛擬機(jī)中 ,對象在內(nèi)存中存儲(chǔ)的布局可以分為三塊區(qū)域:對象頭(Header)
、實(shí)例數(shù)據(jù)(Instance Data)
和對齊填充(Padding)
。
mark-word:對象標(biāo)記字段占4個(gè)字節(jié),用于存儲(chǔ)一些列的標(biāo)記位,比如:哈希值、輕量級鎖的標(biāo)記位,偏向鎖標(biāo)記位、分代年齡等。 Klass Pointer:Class對象的類型指針,Jdk1.8默認(rèn)開啟指針壓縮后為4字節(jié),關(guān)閉指針壓縮(-XX:-UseCompressedOops
)后,長度為8字節(jié)。其指向的位置是對象對應(yīng)的Class對象(其對應(yīng)的元數(shù)據(jù)對象)的內(nèi)存地址。 對象實(shí)際數(shù)據(jù):包括對象的所有成員變量,大小由各個(gè)成員變量決定,比如:byte占1個(gè)字節(jié)8比特位、int占4個(gè)字節(jié)32比特位。 對齊:最后這段空間補(bǔ)全并非必須,僅僅為了起到占位符的作用。由于HotSpot虛擬機(jī)的內(nèi)存管理系統(tǒng)要求對象起始地址必須是8字節(jié)的整數(shù)倍,所以對象頭正好是8字節(jié)的倍數(shù)。因此當(dāng)對象實(shí)例數(shù)據(jù)部分沒有對齊的話,就需要通過對齊填充來補(bǔ)全。
另外 ,在mark-word鎖類型標(biāo)記中,無鎖,偏向鎖,輕量鎖,重量鎖,以及GC標(biāo)記,5種類中沒法用2比特標(biāo)記(2比特最終有4種組合00
、01
、10
、11
),所以無鎖、偏向鎖,前又占了一位偏向鎖標(biāo)記。最終:001為無鎖、101為偏向鎖。
1.2 驗(yàn)證對象結(jié)構(gòu)
為了可以更加直觀的看到對象結(jié)構(gòu),我們可以借助 openjdk
提供的 jol-core
進(jìn)行打印分析。
引入POM
< ! -- https: / / mvnrepository. com/ artifact/ org. openjdk. jol/ jol- cli -- >
< dependency>
< groupId> org. openjdk. jol< / groupId>
< artifactId> jol- cli< / artifactId>
< version> 0.14 < / version>
< / dependency>
測試代碼
public static void main ( String[ ] args) {
System. out. println ( VM. current ( ) . details ( ) ) ;
Object obj = new Object ( ) ;
System. out. println ( obj + " 十六進(jìn)制哈希:" + Integer. toHexString ( obj. hashCode ( ) ) ) ;
System. out. println ( ClassLayout. parseInstance ( obj) . toPrintable ( ) ) ;
}
1.2.1 指針壓縮開啟(默認(rèn))
運(yùn)行結(jié)果
# Running 64 - bit HotSpot VM.
# Using compressed oop with 3 - bit shift.
# Using compressed klass with 3 - bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4 , 1 , 1 , 2 , 2 , 4 , 4 , 8 , 8 [ bytes]
# Array element sizes: 4 , 1 , 1 , 2 , 2 , 4 , 4 , 8 , 8 [ bytes]
java. lang. Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 ( object header) 01 00 00 00 ( 00000001 00000000 00000000 00000000 ) ( 1 )
4 4 ( object header) 00 00 00 00 ( 00000000 00000000 00000000 00000000 ) ( 0 )
8 4 ( object header) e5 01 00 f8 ( 11100101 00000001 00000000 11111000 ) ( - 134217243 )
12 4 ( loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Object對象,總共占16字節(jié) 對象頭占 12 個(gè)字節(jié),其中:mark-word 占 8 字節(jié)、Klass Point 占 4 字節(jié) 最后 4 字節(jié),用于數(shù)據(jù)填充找齊
1.2.2 指針壓縮關(guān)閉
在 Run-->Edit Configurations->VM Options
配置參數(shù) -XX:-UseCompressedOops
關(guān)閉指針壓縮。
運(yùn)行結(jié)果
java. lang. Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 ( object header) 01 12 0 c 53 ( 00000001 00010010 00001100 01010011 ) ( 1393299969 )
4 4 ( object header) 02 00 00 00 ( 00000010 00000000 00000000 00000000 ) ( 2 )
8 4 ( object header) 00 1 c b9 1 b ( 00000000 00011100 10111001 00011011 ) ( 465116160 )
12 4 ( object header) 00 00 00 00 ( 00000000 00000000 00000000 00000000 ) ( 0 )
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
關(guān)閉指針壓縮后,mark-word 還是占 8 字節(jié)不變。 重點(diǎn)在類型指針 Klass Point 的變化,由原來的 4 字節(jié),現(xiàn)在擴(kuò)增到 8 字節(jié)。
1.2.3 對象頭哈希值存儲(chǔ)驗(yàn)證
接下來,我們調(diào)整下測試代碼,看下哈希值在對象頭中具體是怎么存放的。
測試代碼
public static void main ( String[ ] args) {
System. out. println ( VM. current ( ) . details ( ) ) ;
Object obj = new Object ( ) ;
System. out. println ( obj + " 十六進(jìn)制哈希:" + Integer. toHexString ( obj. hashCode ( ) ) ) ;
System. out. println ( ClassLayout. parseInstance ( obj) . toPrintable ( ) ) ;
}
改動(dòng)不多,只是把哈希值和對象打印出來,方便我們驗(yàn)證對象頭關(guān)于哈希值的存放結(jié)果。
運(yùn)行結(jié)果
如圖 15-3,對象的哈希值是16進(jìn)制的,0x2530c12
在對象頭哈希值存放的結(jié)果上看,也有對應(yīng)的數(shù)值。只不過這個(gè)結(jié)果是倒過來的。
關(guān)于這個(gè)倒過來的問題是因?yàn)?#xff0c;大小端存儲(chǔ)導(dǎo)致;
Big-Endian:高位字節(jié)存放于內(nèi)存的低地址端,低位字節(jié)存放于內(nèi)存的高地址端 Little-Endian:低位字節(jié)存放于內(nèi)存的低地址端,高位字節(jié)存放于內(nèi)存的高地址端
mark-word結(jié)構(gòu)
如圖 15-5 最右側(cè)的 3 Bit(1 Bit標(biāo)識偏向鎖,2 Bit描述鎖的類型)是跟鎖類型和GC標(biāo)記相關(guān)的,而 synchronized 的鎖優(yōu)化升級膨脹就是修改的這三位上的標(biāo)識,來區(qū)分不同的鎖類型。從而采取不同的策略來提升性能。
1.3 Monitor 對象
在HotSpot虛擬機(jī)中,monitor是由C++中ObjectMonitor實(shí)現(xiàn)。
synchronized 的運(yùn)行機(jī)制,就是當(dāng) JVM 監(jiān)測到對象在不同的競爭狀況時(shí),會(huì)自動(dòng)切換到適合的鎖實(shí)現(xiàn),這種切換就是鎖的升級、降級。
那么三種不同的 Monitor 實(shí)現(xiàn),也就是常說的三種不同的鎖:偏斜鎖(Biased Locking)、輕量級鎖和重量級鎖。當(dāng)一個(gè) Monitor 被某個(gè)線程持有后,它便處于鎖定狀態(tài)。
Monitor 主要數(shù)據(jù)結(jié)構(gòu)如下 :
// initialize the monitor, exception the semaphore, all other fields
// are simple integers or pointers
ObjectMonitor ( ) {
_header = NULL;
_count = 0 ; // 記錄個(gè)數(shù)
_waiters = 0 ,
_recursions = 0 ; // 線程重入次數(shù)
_object = NULL; // 存儲(chǔ) Monitor 對象
_owner = NULL; // 持有當(dāng)前線程的 owner
_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 ;
_previous_owner_tid = 0 ;
}
源碼地址 :jdk8/hotspot/file/vm/runtime/objectMonitor.hpp
ObjectMonitor,有兩個(gè)隊(duì)列:_WaitSet
、_EntryList
,用來保存 ObjectWaiter 對象列表。 _owner,獲取 Monitor 對象的線程進(jìn)入 _owner 區(qū)時(shí), _count + 1。如果線程調(diào)用了 wait() 方法,此時(shí)會(huì)釋放 Monitor 對象, _owner 恢復(fù)為空, _count - 1。同時(shí)該等待線程進(jìn)入 _WaitSet 中,等待被喚醒。
鎖🔒執(zhí)行效果如下 :
如圖 15-06,每個(gè) Java 對象頭中都包括 Monitor 對象(存儲(chǔ)的指針的指向),synchronized 也就是通過這一種方式獲取鎖,也就解釋了為什么 synchronized() 括號里放任何對象都能獲得鎖🔒!
2. synchronized 特性
2.1 原子性
原子性 是指一個(gè)操作是不可中斷的,要么全部執(zhí)行成功要么全部執(zhí)行失敗。
案例代碼
private static volatile int counter = 0 ;
public static void main ( String[ ] args) throws InterruptedException {
for ( int i = 0 ; i < 10 ; i++ ) {
Thread thread = new Thread ( ( ) - > {
for ( int i1 = 0 ; i1 < 10000 ; i1++ ) {
add ( ) ;
}
} ) ;
thread. start ( ) ;
}
// 等10個(gè)線程運(yùn)行完畢
Thread. sleep ( 1000 ) ;
System. out. println ( counter) ;
}
public static void add ( ) {
counter++ ;
}
這段代碼開啟了 10 個(gè)線程來累加 counter,按照預(yù)期結(jié)果應(yīng)該是 100000。但實(shí)際運(yùn)行會(huì)發(fā)現(xiàn),counter 值每次運(yùn)行都小于 10000,這是因?yàn)?volatile 并不能保證原子性,所以最后的結(jié)果不會(huì)是10000。
修改方法 add(),添加 synchronized:
public static void add ( ) {
synchronized ( AtomicityTest. class ) {
counter++ ;
}
}
這回測試結(jié)果就是:100000 了!
因?yàn)?synchronized 可以保證統(tǒng)一時(shí)間只有一個(gè)線程能拿到鎖,進(jìn)入到代碼塊執(zhí)行。
反編譯查看指令碼
javap -v -p AtomicityTest
public static void add ( ) ;
descriptor: ( ) V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack= 2 , locals= 2 , args_size= 0
0 : ldc #12 // class org/itstack/interview/AtomicityTest
2 : dup
3 : astore_0
4 : monitorenter
5 : getstatic #10 // Field counter:I
8 : iconst_1
9 : iadd
10 : putstatic #10 // Field counter:I
13 : aload_0
14 : monitorexit
15 : goto 23
18 : astore_1
19 : aload_0
20 : monitorexit
21 : aload_1
22 : athrow
23 : return
Exception table:
同步方法
ACC_SYNCHRONIZED
這是一個(gè)同步標(biāo)識,對應(yīng)的16進(jìn)制值是 0x0020
這10個(gè)線程進(jìn)入這個(gè)方法時(shí),都會(huì)判斷是否有此標(biāo)識,然后開始競爭 Monitor 對象。
同步代碼
monitorenter
,在判斷擁有同步標(biāo)識 ACC_SYNCHRONIZED
搶先進(jìn)入此方法的線程會(huì)優(yōu)先擁有 Monitor 的 owner ,此時(shí)計(jì)數(shù)器 +1。monitorexit
,當(dāng)執(zhí)行完退出后,計(jì)數(shù)器 -1,歸 0 后被其他進(jìn)入的線程獲得。
2.2 可見性
在上一章節(jié) volatile 篇中,我們知道它保證變量對所有線程的可見性。最終的效果就是在添加 volatile 的屬性變量時(shí),線程A修改值后,線程B使用此變量可以做出相應(yīng)的反應(yīng),比如 while(!變量)
退出。
那么,synchronized
具備可見性嗎,我們做給例子。
public static boolean sign = false ;
public static void main ( String[ ] args) {
Thread Thread01 = new Thread ( ( ) - > {
int i = 0 ;
while ( ! sign) {
i++ ;
add ( i) ;
}
} ) ;
Thread Thread02 = new Thread ( ( ) - > {
try {
Thread. sleep ( 3000 ) ;
} catch ( InterruptedException ignore) {
}
sign = true ;
logger. info ( "vt.sign = true while (!sign)" )
} ) ;
Thread01. start ( ) ;
Thread02. start ( ) ;
}
public static int add ( int i) {
return i + 1 ;
}
這是兩個(gè)線程操作一個(gè)變量的例子,因?yàn)榫€程間對變量 sign
的不可見性,線程 Thread01 中的 while (!sign) 會(huì)一直執(zhí)行,不會(huì)隨著線程 Thread02 修改 sign = true 而退出循環(huán)。
現(xiàn)在 我們給方法 add 添加 synchronized
關(guān)鍵字修飾,如下:
public static synchronized int add ( int i) {
return i + 1 ;
}
添加后運(yùn)行結(jié)果 :
23 : 55 : 33.849 [ Thread- 1 ] INFO org. itstack. interview. VisibilityTest - vt. sign = true while ( ! sign)
Process finished with exit code 0
可以看到當(dāng)線程 Thread02 改變變量 sign = true 后,線程 Thread01 立即退出了循環(huán)。
注意:不要在方法中添加 System.out.println() ,因?yàn)檫@個(gè)方法中含有 synchronized 會(huì)影響測試結(jié)果!
那么為什么添加 synchronized 也能保證變量的可見性呢?
因?yàn)?#xff1a;
線程解鎖前,必須把共享變量的最新值刷新到主內(nèi)存中。 線程加鎖前,將清空工作內(nèi)存中共享變量的值,從而使用共享變量時(shí)需要從主內(nèi)存中重新讀取最新的值。 volatile 的可見性都是通過內(nèi)存屏障(Memnory Barrier)來實(shí)現(xiàn)的。 synchronized 靠操作系統(tǒng)內(nèi)核互斥鎖實(shí)現(xiàn),相當(dāng)于 JMM 中的 lock、unlock。退出代碼塊時(shí)刷新變量到主內(nèi)存。
2.3 有序性
as-if-serial
,保證不管編譯器和處理器為了性能優(yōu)化會(huì)如何進(jìn)行指令重排序,都需要保證單線程下的運(yùn)行結(jié)果的正確性。也就是常說的:如果在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個(gè)線程觀察另一個(gè)線程,所有的操作都是無序的。
這里有一段雙重檢驗(yàn)鎖(Double-checked Locking)的經(jīng)典案例:
public class Singleton {
private Singleton ( ) {
}
private volatile static Singleton instance;
public Singleton getInstance ( ) {
if ( instance == null) {
synchronized ( Singleton. class ) {
if ( instance == null) {
instance = new Singleton ( ) ;
}
}
}
return instance;
}
}
為什么 ,synchronized 也有可見性的特點(diǎn),還需要 volatile 關(guān)鍵字?
因?yàn)?#xff0c;synchronized 的有序性,不是 volatile 的防止指令重排序。
那如果不加 volatile 關(guān)鍵字可能導(dǎo)致的結(jié)果,就是第一個(gè)線程在初始化初始化對象,設(shè)置 instance 指向內(nèi)存地址時(shí)。第二個(gè)線程進(jìn)入時(shí),有指令重排。在判斷 if (instance == null) 時(shí)就會(huì)有出錯(cuò)的可能,因?yàn)檫@會(huì)可能 instance 可能還沒有初始化成功。
2.4 可重入性
synchronized 是可重入鎖,也就是說,允許一個(gè)線程二次請求自己持有對象鎖的臨界資源,這種情況稱為可重入鎖🔒。
那么我們就寫一個(gè)例子,來證明這樣的情況。
public class ReentryTest extends A {
public static void main ( String[ ] args) {
ReentryTest reentry = new ReentryTest ( ) ;
reentry. doA ( ) ;
}
public synchronized void doA ( ) {
System. out. println ( "子類方法:ReentryTest.doA() ThreadId:" + Thread. currentThread ( ) . getId ( ) ) ;
doB ( ) ;
}
private synchronized void doB ( ) {
super . doA ( ) ;
System. out. println ( "子類方法:ReentryTest.doB() ThreadId:" + Thread. currentThread ( ) . getId ( ) ) ;
}
}
class A {
public synchronized void doA ( ) {
System. out. println ( "父類方法:A.doA() ThreadId:" + Thread. currentThread ( ) . getId ( ) ) ;
}
}
測試結(jié)果
子類方法:ReentryTest. doA ( ) ThreadId:1
父類方法:A. doA ( ) ThreadId:1
子類方法:ReentryTest. doB ( ) ThreadId:1
Process finished with exit code 0
這段單例代碼是遞歸調(diào)用含有 synchronized 鎖的方法,從運(yùn)行正常的測試結(jié)果看,并沒有發(fā)生死鎖。所有可以證明 synchronized 是可重入鎖。
synchronized鎖對象的時(shí)候有個(gè)計(jì)數(shù)器,他會(huì)記錄下線程獲取鎖的次數(shù),在執(zhí)行完對應(yīng)的代碼塊之后,計(jì)數(shù)器就會(huì)-1,直到計(jì)數(shù)器清零,就釋放鎖了。
之所以 ,是可以重入。是因?yàn)?synchronized 鎖對象有個(gè)計(jì)數(shù)器,會(huì)隨著線程獲取鎖后 +1 計(jì)數(shù),當(dāng)線程執(zhí)行完畢后 -1,直到清零釋放鎖。
3. 鎖升級過程
關(guān)于 synchronized 鎖🔒升級有一張非常完整的圖,可以參考:
synchronized 鎖有四種交替升級的狀態(tài):無鎖、偏向鎖、輕量級鎖和重量級,這幾個(gè)狀態(tài)隨著競爭情況逐漸升級。
3.1 偏向鎖
synchronizer源碼:/src/share/vm/runtime/synchronizer.cpp
// NOTE: must use heavy weight monitor to handle jni monitor exit
void ObjectSynchronizer: : jni_exit ( oop obj, Thread* THREAD) {
TEVENT ( jni_exit) ;
if ( UseBiasedLocking) {
Handle h_obj ( THREAD, obj) ;
BiasedLocking: : revoke_and_rebias ( h_obj, false , THREAD) ;
obj = h_obj ( ) ;
}
assert ( ! obj- > mark ( ) - > has_bias_pattern ( ) , "biases should be revoked by now" ) ;
ObjectMonitor* monitor = ObjectSynchronizer: : inflate ( THREAD, obj) ;
// If this thread has locked the object, exit the monitor. Note: can't use
// monitor->check(CHECK); must exit even if an exception is pending.
if ( monitor- > check ( THREAD) ) {
monitor- > exit ( true , THREAD) ;
}
}
UseBiasedLocking 是一個(gè)偏向鎖檢查,1.6之后是默認(rèn)開啟的,1.5中是關(guān)閉的,需要手動(dòng)開啟參數(shù)是 XX:-UseBiasedLocking=false
偏斜鎖會(huì)延緩 JIT 預(yù)熱進(jìn)程,所以很多性能測試中會(huì)顯式地關(guān)閉偏斜鎖,偏斜鎖并不適合所有應(yīng)用場景,撤銷操作(revoke)是比較重的行為,只有當(dāng)存在較多不會(huì)真正競爭的 synchronized 塊兒時(shí),才能體現(xiàn)出明顯改善。
3.2 輕量級鎖
當(dāng)鎖是偏向鎖的時(shí)候,被另一個(gè)線程所訪問,偏向鎖就會(huì)升級為輕量級鎖,其他線程會(huì)通過自旋的形式嘗試獲取鎖,不會(huì)阻塞,提高性能。
在代碼進(jìn)入同步塊的時(shí)候,如果同步對象鎖狀態(tài)為無鎖狀態(tài)(鎖標(biāo)志位為“01”狀態(tài),是否為偏向鎖為“0”),JVM虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對象目前的Mark Word的拷貝,官方稱之為 Displaced Mark Word。
3.3 自旋鎖
自旋鎖是指嘗試獲取鎖的線程不會(huì)立即阻塞,而是采用循環(huán)的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點(diǎn)是循環(huán)會(huì)消耗CPU。
自旋鎖的默認(rèn)大小是10次,可以調(diào)整:-XX:PreBlockSpin
如果自旋n次失敗了,就會(huì)升級為重量級的鎖。重量級的鎖,在 1.3 Monitor 對象中已經(jīng)介紹。
3.4 鎖會(huì)降級嗎?
之前一直了解到 Java 不會(huì)進(jìn)行鎖降級,但最近整理了大量的資料發(fā)現(xiàn)鎖降級確實(shí)是會(huì)發(fā)生。
When safepoints are used?
Below are few reasons for HotSpot JVM to initiate a safepoint:
Garbage collection pauses
Code deoptimization
Flushing code cache
Class redefinition ( e. g. hot swap or instrumentation)
Biased lock revocation
Various debug operation ( e. g. deadlock check or stacktrace dump)
Biased lock revocation
,當(dāng) JVM 進(jìn)入安全點(diǎn) SafePoint 的時(shí)候,會(huì)檢查是否有閑置的 Monitor,然后試圖進(jìn)行降級。
四、總結(jié)
本章關(guān)于 synchronized
鎖涉及到了較多的C++源碼分析學(xué)習(xí),源碼地址:https://github.com/JetBrains/jdk8u_hotspot 關(guān)于鎖的細(xì)節(jié)挖掘除了本文提到的還有很多知識點(diǎn)可以繼續(xù)學(xué)習(xí),可以結(jié)合 ifeve、并發(fā)編程、深入理解JVM虛擬機(jī),等系列知識整理。 學(xué)習(xí)過程中結(jié)合C++源代碼中關(guān)于鎖的實(shí)現(xiàn),更容易理解可能原本晦澀難懂的概念。在結(jié)合實(shí)際的案例驗(yàn)證,會(huì)容易接受這部分知識。 好了,這篇就寫到這里了,如果有觀點(diǎn)和文章不準(zhǔn)確的表達(dá)歡迎留言,互相學(xué)習(xí),互相掃盲,互相進(jìn)步。
五、傅詩一手
會(huì)所🏢,里的碼農(nóng)會(huì)鎖。 擁擠🤼?♂?,就需加價(jià)升級。 項(xiàng)目🤯,按摩對象頭皮。 效果🤨,可見原子有序。
六、系列推薦
認(rèn)知自己的技術(shù)棧盲區(qū) 握草,你竟然在代碼里下毒! 一次代碼評審,差點(diǎn)過不了試用期! ThreadLocal 技術(shù)棧深度學(xué)習(xí) HashMap核心知識,擾動(dòng)函數(shù)、負(fù)載因子、擴(kuò)容鏈表拆分