Synchronized
本篇文章將圍繞synchronized關(guān)鍵字,使用大量圖片、案例深入淺出的描述CAS、synchronized Java層面和C++層面的實(shí)現(xiàn)、鎖升級(jí)的原理、源碼等
大概觀看時(shí)間17分鐘
可以帶著幾個(gè)問題去查看本文,如果認(rèn)真看完,問題都會(huì)迎刃而解:
1、synchronized是怎么使用的?在Java層面是如何實(shí)現(xiàn)?
2、CAS是什么?能帶來什么好處?又有什么缺點(diǎn)?
3、mark word是什么?跟synchronized有啥關(guān)系?
4、synchronized的鎖升級(jí)優(yōu)化是什么?在C++層面如何實(shí)現(xiàn)?
5、JDK 8 中輕量級(jí)鎖CAS失敗到底會(huì)不會(huì)自旋?
6、什么是object monitor?wait/notify方法是如何實(shí)現(xiàn)的?使用synchronized時(shí),線程阻塞后是如何在阻塞隊(duì)列中排序的?
...
synchronized Java層面實(shí)現(xiàn)
synchronized作用在代碼塊或方法上,用于保證并發(fā)環(huán)境下的同步機(jī)制
任何線程遇到synchronized都要先獲取到鎖才能執(zhí)行代碼塊或方法中的操作
在Java中每個(gè)對(duì)象有一個(gè)對(duì)應(yīng)的monitor對(duì)象(監(jiān)視器),當(dāng)獲取到A對(duì)象的鎖時(shí),A對(duì)象的監(jiān)視器對(duì)象中有個(gè)字段會(huì)指向當(dāng)前線程,表示這個(gè)線程獲取到A對(duì)象的鎖(詳細(xì)原理后文描述)
synchronized可以作用于普通對(duì)象和靜態(tài)對(duì)象,當(dāng)作用于靜態(tài)對(duì)象、靜態(tài)方法時(shí),都是去獲取其對(duì)應(yīng)的Class對(duì)象的鎖
synchronized作用在代碼塊上時(shí),會(huì)使用monitorentry和monitorexit字節(jié)碼指令來標(biāo)識(shí)加鎖、解鎖
synchronized作用在方法上時(shí),會(huì)在訪問標(biāo)識(shí)上加上synchronized
指令中可能出現(xiàn)兩個(gè)monitorexit指令是因?yàn)楫?dāng)發(fā)生異常時(shí),會(huì)自動(dòng)執(zhí)行monitorexit進(jìn)行解鎖
正常流程是PC 12-14,如果在此期間出現(xiàn)異常就會(huì)跳轉(zhuǎn)到PC 17,最終在19執(zhí)行monitorexit進(jìn)行解鎖
Object obj = new Object();
synchronized (obj) {
}

在上篇文章中我們說過原子性、可見性以及有序性
synchronized加鎖解鎖的字節(jié)碼指令使用屏障,加鎖時(shí)共享內(nèi)存從主內(nèi)存中重新讀取,解鎖前把工作內(nèi)存數(shù)據(jù)寫回主內(nèi)存以此來保證可見性
由于獲取到鎖才能執(zhí)行相當(dāng)于串行執(zhí)行,也就保證原子性和有序性,需要注意的是加鎖與解鎖之間的指令還是可以重排序的
CAS
為了更好的說明synchronized原理和鎖升級(jí),我們先來聊聊CAS
在上篇文章中我們說過,volatile不能保證復(fù)合操作的原子性,使用synchronized方法或者CAS能夠保證復(fù)合操作原子性
那什么是CAS呢?
CAS全稱 Compare And Swap 比較并交換,讀取數(shù)據(jù)后要修改時(shí)用讀取的數(shù)據(jù)和地址上的值進(jìn)行比較,如果相等那就將地址上的值替換為目標(biāo)值,如果不相等,通常會(huì)重新讀取數(shù)據(jù)再進(jìn)行CAS操作,也就是失敗重試
synchronized加鎖是一種悲觀策略,每次遇到時(shí)都認(rèn)為會(huì)有并發(fā)問題,要先獲取鎖才操作
而CAS是一種樂觀策略,每次先大膽的去操作,操作失敗(CAS失?。┰偈褂醚a(bǔ)償措施(失敗重試)
CAS與失敗重試(循環(huán))的組合構(gòu)成樂觀鎖或者說自旋鎖(循環(huán)嘗試很像在自我旋轉(zhuǎn))
并發(fā)包下的原子類,依靠Unsafe大量使用CAS操作,比如AtomicInteger的自增
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//var1是調(diào)用方法的對(duì)象,var2是需要讀取/修改的值在這個(gè)對(duì)象上的偏移量,var4是自增1
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//var5是通過對(duì)象和字段偏移量獲取到字段最新值
var5 = this.getIntVolatile(var1, var2);
//cas:var1,var2找到字段的值 與 var5比較,相等就替換為 var5+var4
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
CAS只能對(duì)一個(gè)變量進(jìn)行操作,如果要對(duì)多個(gè)變量進(jìn)行操作,那么只能對(duì)外封裝一層(將多個(gè)變量封裝為新對(duì)象的字段),再使用原子類中的AtomicReference
不知各位同學(xué)有沒有發(fā)現(xiàn),CAS的流程有個(gè)bug,就是在讀數(shù)據(jù)與比較數(shù)據(jù)之間,如果數(shù)據(jù)從A被改變到B,再改變到A,那么CAS也能執(zhí)行成功
這種場(chǎng)景有的業(yè)務(wù)能夠接受,有的業(yè)務(wù)無法接受,這就是所謂的ABA問題
而解決ABA問題的方式比較簡(jiǎn)單,可以再比較時(shí)附加一個(gè)自增的版本號(hào),JDK也提供解決ABA問題的原子類AtomicStampedReference
CAS能夠避免線程阻塞,但如果一直失敗就會(huì)一直循環(huán),增加CPU的開銷,CAS失敗后重試的次數(shù)/時(shí)長(zhǎng)不好評(píng)估
因此CAS操作適用于競(jìng)爭(zhēng)小的場(chǎng)景,用CPU空轉(zhuǎn)的開銷來換取線程阻塞掛起/恢復(fù)的開銷
鎖升級(jí)
早期版本的synchronized會(huì)將獲取不到鎖的線程直接掛起,性能不好
JDK 6 時(shí)對(duì)synchronized的實(shí)現(xiàn)進(jìn)行優(yōu)化,也就是鎖升級(jí)
鎖的狀態(tài)可以分為無鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖
可以暫時(shí)把重量級(jí)鎖理解為早期獲取不到鎖就讓線程掛起,新的優(yōu)化也就是輕量級(jí)鎖和偏向鎖
mark word
為了更好的說明鎖升級(jí),我們先來聊聊Java對(duì)象頭中的mark word
我們下面的探究都是圍繞64位的虛擬機(jī)
Java對(duì)象的內(nèi)存由mark word、klass word、如果是數(shù)組還要記錄長(zhǎng)度、實(shí)例數(shù)據(jù)(字段)、對(duì)其填充(填充到8字節(jié)倍數(shù))組成
mark word會(huì)記錄鎖狀態(tài),在不同鎖狀態(tài)的情況下記錄的數(shù)據(jù)也不同
下面這個(gè)表格是從無鎖到重量級(jí)鎖mark word記錄的內(nèi)容
|----------------------------------------------------------------------|--------|--------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | 無鎖
|----------------------------------------------------------------------|--------|--------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | 偏向鎖
|----------------------------------------------------------------------|--------|--------|
| ptr_to_lock_record:62 | lock:2 | 輕量級(jí)鎖
|----------------------------------------------------------------------|--------|--------|
| ptr_to_heavyweight_monitor:62 | lock:2 | 重量級(jí)鎖
|----------------------------------------------------------------------|--------|--------|
unused 表示還沒使用
identity_hashcode 用于記錄一致性哈希
age 用于記錄GC年齡
biased_lock 標(biāo)識(shí)是否使用偏向鎖,0表示未開啟,1表示開啟
lock 用于標(biāo)識(shí)鎖狀態(tài)標(biāo)志位,01無鎖或偏向鎖、00輕量級(jí)鎖、10重量級(jí)鎖
thread 用于標(biāo)識(shí)偏向的線程
epoch 記錄偏向的時(shí)間戳
ptr_to_lock_record 記錄棧幀中的鎖記錄(后文介紹)
ptr_to_heavyweight_monitor 記錄獲取重量級(jí)鎖的線程
jol查看mark word
比較熟悉mark word的同學(xué)可以跳過
了解mark word后再來熟悉下不同鎖狀態(tài)下的mark word,我使用的是jol查看內(nèi)存
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.12</version>
</dependency>
無鎖
各位同學(xué)實(shí)驗(yàn)時(shí)的mark word可能和我注釋中的不同,我們主要查看鎖標(biāo)識(shí)的值和是否啟用偏向鎖

public void noLock() {
Object obj = new Object();
//mark word 00000001 被unused:1,age:4,biased_lock:1,lock:2使用,001表示0未啟用偏向鎖,01表示無鎖
//01 00 00 00 (00000001 00000000 00000000 00000000)
//00 00 00 00 (00000000 00000000 00000000 00000000)
ClassLayout objClassLayout = ClassLayout.parseInstance(obj);
System.out.println(objClassLayout.toPrintable());
//計(jì)算一致性哈希后
//01 b6 ce a8
//6a 00 00 00
obj.hashCode();
System.out.println(objClassLayout.toPrintable());
//進(jìn)行GC 查看GC年齡 0 0001 0 01 前2位表示鎖狀態(tài)01無鎖,第三位biased_lock為0表示未啟用偏向鎖,后續(xù)四位則是GC年齡age 1
//09 b6 ce a8 (00001001 10110110 11001110 10101000)
//6a 00 00 00 (01101010 00000000 00000000 00000000)
System.gc();
System.out.println(objClassLayout.toPrintable());
}
輕量級(jí)鎖
public void lightLockTest() throws InterruptedException {
Object obj = new Object();
ClassLayout objClassLayout = ClassLayout.parseInstance(obj);
//1334729950
System.out.println(obj.hashCode());
//0 01 無鎖
//01 4e c0 d5 (00000001 01001110 11000000 11010101)
//6a 00 00 00 (01101010 00000000 00000000 00000000)
System.out.println(Thread.currentThread().getName() + ":");
System.out.println(objClassLayout.toPrintable());
Thread thread1 = new Thread(() -> {
synchronized (obj) {
// 110110 00 中的00表示輕量級(jí)鎖其他62位指向擁有鎖的線程
//d8 f1 5f 1d (11011000 11110001 01011111 00011101)
//00 00 00 00 (00000000 00000000 00000000 00000000)
System.out.println(Thread.currentThread().getName() + ":");
System.out.println(objClassLayout.toPrintable());
//1334729950
//無鎖升級(jí)成輕量級(jí)鎖后 hashcode未變 對(duì)象頭中沒存儲(chǔ)hashcode 只存儲(chǔ)擁有鎖的線程
//(實(shí)際上mark word內(nèi)容被存儲(chǔ)到lock record中,所以hashcode也被存儲(chǔ)到lock record中)
System.out.println(obj.hashCode());
}
}, "t1");
thread1.start();
//等待t1執(zhí)行完 避免 發(fā)生競(jìng)爭(zhēng)
thread1.join();
//輕量級(jí)鎖 釋放后 mark word 恢復(fù)成無鎖 存儲(chǔ)哈希code的狀態(tài)
//01 4e c0 d5 (00000001 01001110 11000000 11010101)
//6a 00 00 00 (01101010 00000000 00000000 00000000)
System.out.println(Thread.currentThread().getName() + ":");
System.out.println(objClassLayout.toPrintable());
Thread thread2 = new Thread(() -> {
synchronized (obj) {
//001010 00 中的00表示輕量級(jí)鎖其他62位指向擁有鎖的線程
//28 f6 5f 1d (00101000 11110110 01011111 00011101)
//00 00 00 00 (00000000 00000000 00000000 00000000)
System.out.println(Thread.currentThread().getName() + ":");
System.out.println(objClassLayout.toPrintable());
}
}, "t2");
thread2.start();
thread2.join();
}
偏向鎖
public void biasedLockTest() throws InterruptedException {
//延遲讓偏向鎖啟動(dòng)
Thread.sleep(5000);
Object obj = new Object();
ClassLayout objClassLayout = ClassLayout.parseInstance(obj);
//1 01 匿名偏向鎖 還未設(shè)置偏向線程
//05 00 00 00 (00000101 00000000 00000000 00000000)
//00 00 00 00 (00000000 00000000 00000000 00000000)
System.out.println(Thread.currentThread().getName() + ":");
System.out.println(objClassLayout.toPrintable());
synchronized (obj) {
//偏向鎖 記錄 線程地址
//05 30 e3 02 (00000101 00110000 11100011 00000010)
//00 00 00 00 (00000000 00000000 00000000 00000000)
System.out.println(Thread.currentThread().getName() + ":");
System.out.println(objClassLayout.toPrintable());
}
Thread thread1 = new Thread(() -> {
synchronized (obj) {
//膨脹為輕量級(jí) 0 00 0未啟用偏向鎖,00輕量級(jí)鎖
//68 f4 a8 1d (01101000 11110100 10101000 00011101)
//00 00 00 00 (00000000 00000000 00000000 00000000)
System.out.println(Thread.currentThread().getName() + ":");
System.out.println(objClassLayout.toPrintable());
}
}, "t1");
thread1.start();
thread1.join();
}
重量級(jí)鎖
public void heavyLockTest() throws InterruptedException {
Object obj = new Object();
ClassLayout objClassLayout = ClassLayout.parseInstance(obj);
Thread thread1 = new Thread(() -> {
synchronized (obj) {
//第一次 00 表示 輕量級(jí)鎖
//d8 f1 c3 1e (11011000 11110001 11000011 00011110)
//00 00 00 00 (00000000 00000000 00000000 00000000)
System.out.println(Thread.currentThread().getName() + ":");
System.out.println(objClassLayout.toPrintable());
//用debug控制t2來競(jìng)爭(zhēng)
//第二次打印 變成 10 表示膨脹為重量級(jí)鎖(t2競(jìng)爭(zhēng)) 其他62位指向監(jiān)視器對(duì)象
//fa 21 3e 1a (11111010 00100001 00111110 00011010)
//00 00 00 00 (00000000 00000000 00000000 00000000)
System.out.println(Thread.currentThread().getName() + ":");
System.out.println(objClassLayout.toPrintable());
}
}, "t1");
thread1.start();
Thread thread2 = new Thread(() -> {
synchronized (obj) {
//t2競(jìng)爭(zhēng) 膨脹為 重量級(jí)鎖 111110 10 10為重量級(jí)鎖
//fa 21 3e 1a (11111010 00100001 00111110 00011010)
//00 00 00 00 (00000000 00000000 00000000 00000000)
System.out.println(Thread.currentThread().getName() + ":");
System.out.println(objClassLayout.toPrintable());
}
}, "t2");
thread2.start();
thread1.join();
thread2.join();
//10 重量級(jí)鎖 未發(fā)生鎖降級(jí)
//3a 36 4d 1a (00111010 00110110 01001101 00011010)
//00 00 00 00 (00000000 00000000 00000000 00000000)
System.out.println(Thread.currentThread().getName() + ":");
System.out.println(objClassLayout.toPrintable());
}
輕量級(jí)鎖
輕量級(jí)鎖的提出是為了減小傳統(tǒng)重量級(jí)鎖使用互斥量(掛起/恢復(fù)線程)所產(chǎn)生的開銷
面對(duì)較少的競(jìng)爭(zhēng)場(chǎng)景時(shí),獲取鎖的時(shí)間總是短暫的,而掛起線程用戶態(tài)、內(nèi)核態(tài)的開銷比較大,使用輕量級(jí)鎖減少開銷
那么輕量級(jí)鎖是如何實(shí)現(xiàn)的呢?
輕量級(jí)鎖主要由lock record、mark word、CAS來實(shí)現(xiàn),lock record存儲(chǔ)在線程的棧幀中,來記錄鎖的信息
加鎖
查看對(duì)象是不是無鎖狀態(tài),如果對(duì)象是無鎖狀態(tài),會(huì)將mark word復(fù)制到lock record鎖記錄中的displaced mark word

然后再嘗試使用CAS嘗試將mark word中部分內(nèi)容替換指向這個(gè)lock record,如果成功表示獲取鎖成功

如果對(duì)象持有鎖,會(huì)查看持有鎖的線程是不是當(dāng)前線程,這種可重入的情況下lock record中記錄不再是mark word而是null
可重入的情況下,只需要進(jìn)行自增計(jì)數(shù)即可,解鎖時(shí)遇到null的lock record則扣減

如果CAS失敗或者持有鎖的線程不是當(dāng)前線程,就會(huì)觸發(fā)鎖膨脹
關(guān)鍵代碼如下:
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
//當(dāng)前對(duì)象的mark word
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
//如果當(dāng)前對(duì)象是無鎖狀態(tài)
if (mark->is_neutral()) {
//將mark word復(fù)制到lock record
lock->set_displaced_header(mark);
//CAS將當(dāng)前對(duì)象的mark word內(nèi)容替換為指向lock record
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
return ;
}
} else
//如果有鎖 判斷是不是當(dāng)前線程獲取鎖
if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
assert(lock != mark->locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
//可重入鎖 復(fù)制null
lock->set_displaced_header(NULL);
return;
}
//有鎖并且獲取鎖的線程不是當(dāng)前線程 或者 CAS失敗 進(jìn)行膨脹
lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
解鎖
查看lock record中復(fù)制的內(nèi)容是不是空,是空說明是可重入鎖
不為空則查看mark word是否指向lock record,如果指向則CAS嘗試將mark word記錄指向lock record替換為lock record中的displaced mark word(也就是原來的mark word)
如果mark word不指向lock record 或者 CAS失敗了 說明存在競(jìng)爭(zhēng),其他線程加鎖失敗讓mark word指向重量級(jí)鎖,直接膨脹

關(guān)鍵代碼如下:
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");
//獲取復(fù)制的mark word
markOop dhw = lock->displaced_header();
markOop mark ;
//如果為空 說明是可重入
if (dhw == NULL) {
// Recursive stack-lock.
// Diagnostics -- Could be: stack-locked, inflating, inflated.
mark = object->mark() ;
assert (!mark->is_neutral(), "invariant") ;
if (mark->has_locker() && mark != markOopDesc::INFLATING()) {
assert(THREAD->is_lock_owned((address)mark->locker()), "invariant") ;
}
if (mark->has_monitor()) {
ObjectMonitor * m = mark->monitor() ;
assert(((oop)(m->object()))->mark() == mark, "invariant") ;
assert(m->is_entered(THREAD), "invariant") ;
}
return ;
}
mark = object->mark() ;
//如果mark word指向lock record
if (mark == (markOop) lock) {
assert (dhw->is_neutral(), "invariant") ;
//嘗試CAS將指向lock record的mark word替換為原來的內(nèi)容
if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
TEVENT (fast_exit: release stacklock) ;
return;
}
}
//未指向當(dāng)前l(fā)ock record或者CAS失敗則膨脹
ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}
偏向鎖
hotspot開發(fā)人員測(cè)試,在某些場(chǎng)景下,總是同一個(gè)線程獲取鎖,在這種場(chǎng)景下,希望用更小的開銷來獲取鎖
當(dāng)開啟偏向鎖后,如果是無鎖狀態(tài)會(huì)將mark word改為偏向某個(gè)線程ID,以此標(biāo)識(shí)這個(gè)線程獲取鎖(鎖偏向這個(gè)線程)
如果正處于偏向鎖,遇到競(jìng)爭(zhēng)可能膨脹為輕量級(jí)鎖,如果要存儲(chǔ)一致性哈希等情況也會(huì)膨脹為重量級(jí)鎖
JDK8默認(rèn)開啟偏向鎖,在高版本JDK默認(rèn)不開啟偏向鎖,可能因?yàn)槠蜴i的維護(hù)超過收益,我們也不深入進(jìn)行研究
重量級(jí)鎖
object monitor
使用object monitor對(duì)象來實(shí)現(xiàn)重量級(jí)鎖
object monitor中使用一些字段記錄信息,比如:object字段用于記錄鎖的那個(gè)對(duì)象,header字段用于記錄鎖的那個(gè)對(duì)象的mark word、owner字段用于記錄持有鎖的線程
object monitor使用阻塞隊(duì)列來存儲(chǔ)競(jìng)爭(zhēng)不到鎖的線程,使用等待隊(duì)列來存儲(chǔ)調(diào)用wait進(jìn)入等待狀態(tài)的線程
阻塞隊(duì)列和等待隊(duì)列類比著并發(fā)包下的AQS和Condition
object monitor使用cxq棧和entry list隊(duì)列來實(shí)現(xiàn)阻塞隊(duì)列,其中cxq棧中存儲(chǔ)有競(jìng)爭(zhēng)的線程,entry list存儲(chǔ)已經(jīng)競(jìng)爭(zhēng)失敗較穩(wěn)定的線程;使用wait set實(shí)現(xiàn)等待隊(duì)列
當(dāng)線程調(diào)用wait時(shí),進(jìn)入wait set等待隊(duì)列
而調(diào)用notify時(shí),只是將等待隊(duì)列的隊(duì)頭節(jié)點(diǎn)加入cxq,并沒有喚醒該線程去競(jìng)爭(zhēng)
真正的喚醒線程是在釋放鎖時(shí),去穩(wěn)定的隊(duì)列entry list中喚醒隊(duì)頭節(jié)點(diǎn)去競(jìng)爭(zhēng),而此時(shí)被喚醒的節(jié)點(diǎn)并不一定能搶到鎖,因?yàn)榫€程進(jìn)入cxq時(shí)還會(huì)通過自旋來搶鎖,以此來實(shí)現(xiàn)非公平鎖
如果穩(wěn)定的entry list中沒有存儲(chǔ)線程,會(huì)將cxq棧中存儲(chǔ)的線程全存儲(chǔ)到entry list中再去喚醒,此時(shí)越晚進(jìn)入cxq的線程反而會(huì)越早被喚醒(cxq棧先進(jìn)后出)
其實(shí)實(shí)現(xiàn)與AQS類似,來看這樣一段代碼:
t1-t6獲取同一把鎖,使用t1線程進(jìn)行阻塞一會(huì),后續(xù)t2-t6線程按照順序啟動(dòng),由于自轉(zhuǎn)獲取不到鎖,它們會(huì)被依次放入cxq:t2,t3,t4,t5,t6
在t1釋放鎖時(shí),由于entry list中沒有線程,于是將cxq中的線程存入entry list:t6,t5,t4,t3,t2,再喚醒t6
由于后續(xù)沒有線程進(jìn)行競(jìng)爭(zhēng),因此最終執(zhí)行順序?yàn)?code>t1,t6,t5,t4,t3,t2
Object obj = new Object();
new Thread(() -> {
synchronized (obj) {
try {
//輸入阻塞
//阻塞的目的是讓 其他線程自旋完未獲取到鎖,進(jìn)入cxq棧
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 獲取到鎖");
}
}, "t1").start();
//sleep控制線程阻塞的順序
Thread.sleep(50);
new Thread(() -> {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + " 獲取到鎖");
}
}, "t2").start();
Thread.sleep(50);
new Thread(() -> {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + " 獲取到鎖");
}
}, "t3").start();
Thread.sleep(50);
new Thread(() -> {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + " 獲取到鎖");
}
}, "t4").start();
Thread.sleep(50);
new Thread(() -> {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + " 獲取到鎖");
}
}, "t5").start();
Thread.sleep(50);
new Thread(() -> {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + " 獲取到鎖");
}
}, "t6").start();
大致了解了下object monitor,我們?cè)賮砜纯磁蛎浐妥孕?/p>
膨脹
在膨脹時(shí)會(huì)有四種狀態(tài),分別是
inflated 已膨脹:mark word鎖標(biāo)志為10(2)說明已膨脹,直接返回object monitor
inflation in progress 膨脹中:如果已經(jīng)有其他線程在膨脹了,就等待一會(huì)循環(huán)后查看狀態(tài)進(jìn)入已膨脹的邏輯
stack-locked 輕量級(jí)鎖膨脹
neutral 無鎖膨脹
輕量級(jí)鎖和無鎖膨脹邏輯差不多,都是需要創(chuàng)建object monitor對(duì)象,并且set一些屬性進(jìn)去(比如:mark word、鎖的哪個(gè)對(duì)象、哪個(gè)線程持有鎖...),最后再使用CAS去替換mark word指向object monitor
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
for (;;) {
const markOop mark = object->mark() ;
assert (!mark->has_bias_pattern(), "invariant") ;
// The mark can be in one of the following states:
// * Inflated - just return
// * Stack-locked - coerce it to inflated
// * INFLATING - busy wait for conversion to complete
// * Neutral - aggressively inflate the object.
// * BIASED - Illegal. We should never see this
// CASE: inflated
// 已膨脹:查看 mark word 后兩位是否為2 是則膨脹完 返回monitor對(duì)象
if (mark->has_monitor()) {
ObjectMonitor * inf = mark->monitor() ;
assert (inf->header()->is_neutral(), "invariant");
assert (inf->object() == object, "invariant") ;
assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
return inf ;
}
// CASE: inflation in progress - inflating over a stack-lock.
// 膨脹中: 等待一會(huì) 再循環(huán) 從膨脹完?duì)顟B(tài)退出
if (mark == markOopDesc::INFLATING()) {
TEVENT (Inflate: spin while INFLATING) ;
ReadStableMark(object) ;
continue ;
}
// CASE: stack-locked
//輕量級(jí)鎖膨脹
if (mark->has_locker()) {
//創(chuàng)建ObjectMonitor
ObjectMonitor * m = omAlloc (Self) ;
m->Recycle();
m->_Responsible = NULL ;
m->OwnerIsThread = 0 ;
m->_recursions = 0 ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/class
//cas將mark word替換指向ObjectMonitor
markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
//cas 失敗 則說明其他線程膨脹成功,刪除當(dāng)前monitor 退出
if (cmp != mark) {
omRelease (Self, m, true) ;
continue ; // Interference -- just retry
}
markOop dmw = mark->displaced_mark_helper() ;
assert (dmw->is_neutral(), "invariant") ;
//成功 設(shè)置mark word
m->set_header(dmw) ;
//設(shè)置持有鎖的線程
m->set_owner(mark->locker());
//設(shè)置鎖的是哪個(gè)對(duì)象
m->set_object(object);
guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;
//修改mark word對(duì)象頭信息 鎖狀態(tài) 2
object->release_set_mark(markOopDesc::encode(m));
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
TEVENT(Inflate: overwrite stacklock) ;
if (TraceMonitorInflation) {
if (object->is_instance()) {
ResourceMark rm;
tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(void *) object, (intptr_t) object->mark(),
object->klass()->external_name());
}
}
return m ;
}
// CASE: neutral
//無鎖膨脹 與輕量級(jí)鎖膨脹類似,也是創(chuàng)建monitor對(duì)象并注入屬性,只是很多屬性為空
assert (mark->is_neutral(), "invariant");
ObjectMonitor * m = omAlloc (Self) ;
m->Recycle();
m->set_header(mark);
m->set_owner(NULL);
m->set_object(object);
m->OwnerIsThread = 1 ;
m->_recursions = 0 ;
m->_Responsible = NULL ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // consider: keep metastats by type/class
//cas 更新 mark word 失敗循環(huán)等待 成功返回
if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
m->set_object (NULL) ;
m->set_owner (NULL) ;
m->OwnerIsThread = 0 ;
m->Recycle() ;
omRelease (Self, m, true) ;
m = NULL ;
continue ;
}
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
TEVENT(Inflate: overwrite neutral) ;
if (TraceMonitorInflation) {
if (object->is_instance()) {
ResourceMark rm;
tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(void *) object, (intptr_t) object->mark(),
object->klass()->external_name());
}
}
return m ;
}
}
自旋
膨脹過后,在最終掛起前會(huì)進(jìn)行固定自旋和自適應(yīng)自旋
固定自旋默認(rèn)10+1次
自適應(yīng)自旋一開始5000次,如果最近競(jìng)爭(zhēng)少獲取到鎖就將自旋次數(shù)調(diào)大,如果最近競(jìng)爭(zhēng)大獲取不到鎖就將自旋次數(shù)調(diào)小
int ObjectMonitor::TrySpin_VaryDuration (Thread * Self) {
// Dumb, brutal spin. Good for comparative measurements against adaptive spinning.
int ctr = Knob_FixedSpin ;
if (ctr != 0) {
while (--ctr >= 0) {
if (TryLock (Self) > 0) return 1 ;
SpinPause () ;
}
return 0 ;
}
//先進(jìn)行固定11自旋次數(shù) 獲取到鎖返回,沒獲取到空轉(zhuǎn)
for (ctr = Knob_PreSpin + 1; --ctr >= 0 ; ) {
if (TryLock(Self) > 0) {
// Increase _SpinDuration ...
// Note that we don't clamp SpinDuration precisely at SpinLimit.
// Raising _SpurDuration to the poverty line is key.
int x = _SpinDuration ;
if (x < Knob_SpinLimit) {
if (x < Knob_Poverty) x = Knob_Poverty ;
_SpinDuration = x + Knob_BonusB ;
}
return 1 ;
}
SpinPause () ;
}
//自適應(yīng)自旋 一開始5000 如果成功認(rèn)為此時(shí)競(jìng)爭(zhēng)不大 自旋獲取鎖成功率高 增加重試次數(shù) 如果失敗則減少
//...
}
總結(jié)
本篇文章圍繞synchronized,深入淺出的描述CAS、synchronized在Java層面和C++層面的實(shí)現(xiàn)、鎖升級(jí)原理、案例、源碼等
synchronized用于并發(fā)下的需要同步的場(chǎng)景,使用它可以滿足原子性、可見性以及有序性,它可以作用在普通對(duì)象和靜態(tài)對(duì)象,作用于靜態(tài)對(duì)象時(shí)是去獲取其對(duì)應(yīng)的Class對(duì)象的鎖
synchronized作用在代碼塊上時(shí),使用monitorentry、monitorexit字節(jié)碼指令來標(biāo)識(shí)加鎖、解鎖;作用在方法上時(shí),在訪問標(biāo)識(shí)加鎖synchronized關(guān)鍵字,虛擬機(jī)隱式使用monitorentry、monitorexit
CAS 比較并替換,常與重試機(jī)制實(shí)現(xiàn)樂觀鎖/自旋鎖,優(yōu)點(diǎn)是能夠在競(jìng)爭(zhēng)小的場(chǎng)景用較小的開銷取代線程掛起,但帶來ABA問題、無法預(yù)估重試次數(shù)空轉(zhuǎn)CPU的開銷等問題
輕量級(jí)鎖的提出是為了在交替執(zhí)行/競(jìng)爭(zhēng)少的場(chǎng)景,用更小的開銷取代互斥量;使用CAS和lock record實(shí)現(xiàn)
輕量級(jí)鎖加鎖時(shí),如果是無鎖則復(fù)制mark word到lock record中,再CAS將對(duì)象mark word替換為指向該lock record,失敗則膨脹;如果已經(jīng)持有鎖則判斷持有鎖的線程是不是當(dāng)前線程,是則累加次數(shù),不是當(dāng)前線程則膨脹
輕量級(jí)鎖解鎖時(shí),查看lock record復(fù)制的是不是null,是則說明是可重入鎖,次數(shù)減一;不是則CAS把復(fù)制過來的mark word替換回去,如果替換失敗說明其他線程競(jìng)爭(zhēng),mark word已經(jīng)指向object monitor,去指向重量級(jí)鎖的釋放
偏向鎖的提出是為了在經(jīng)常一條線程執(zhí)行的場(chǎng)景下,用更小的開銷來取代CAS的開銷,只不過高版本不再默認(rèn)開啟
重量級(jí)鎖由object monitor來實(shí)現(xiàn),object monitor中使用cxq、entry list來構(gòu)成阻塞隊(duì)列,wait set來構(gòu)成等待隊(duì)列
當(dāng)執(zhí)行wait方法時(shí),線程構(gòu)建為節(jié)點(diǎn)加入wait set;當(dāng)執(zhí)行notify方法時(shí),將wait set隊(duì)頭節(jié)點(diǎn)加入cxq,在釋放鎖時(shí)才去喚醒entry list隊(duì)頭節(jié)點(diǎn)競(jìng)爭(zhēng)鎖,即使沒搶到鎖構(gòu)建為節(jié)點(diǎn)加入cxq時(shí)還會(huì)自旋,因此并不是entry list隊(duì)頭節(jié)點(diǎn)就一定能搶到鎖,以此來實(shí)現(xiàn)非公平鎖;當(dāng)entry list為空時(shí),將cxq棧中的節(jié)點(diǎn)加入entry list隊(duì)列(后進(jìn)入cxq的節(jié)點(diǎn)會(huì)被先喚醒)
在膨脹為重量級(jí)鎖時(shí)有四種情況,如果狀態(tài)為已膨脹則直接返回object monitor對(duì)象;如果狀態(tài)為膨脹中,說明其他線程正在膨脹,等待會(huì),下次循環(huán)進(jìn)入已膨脹的邏輯;如果狀態(tài)為輕量級(jí)鎖膨脹或無鎖膨脹,都會(huì)去創(chuàng)建object monitor對(duì)象,set一些重要屬性,并CAS去將mark word替換為指向該object monitor
重量級(jí)鎖在最終掛起前會(huì)進(jìn)行固定自旋和自適應(yīng)自旋(最近競(jìng)爭(zhēng)小就增加自旋次數(shù);競(jìng)爭(zhēng)多就減少自旋次數(shù))
最后(不要白嫖,一鍵三連求求拉~)
本篇文章被收入專欄 由點(diǎn)到線,由線到面,深入淺出構(gòu)建Java并發(fā)編程知識(shí)體系,感興趣的同學(xué)可以持續(xù)關(guān)注喔
本篇文章筆記以及案例被收入 gitee-StudyJava、 github-StudyJava 感興趣的同學(xué)可以stat下持續(xù)關(guān)注喔~
案例地址:
Gitee-JavaConcurrentProgramming/src/main/java/B_synchronized
Github-JavaConcurrentProgramming/src/main/java/B_synchronized
有什么問題可以在評(píng)論區(qū)交流,如果覺得菜菜寫的不錯(cuò),可以點(diǎn)贊、關(guān)注、收藏支持一下~
關(guān)注菜菜的后端私房菜,分享更多干貨