前幾節(jié)你應(yīng)該已經(jīng)了解和掌握了Thread社牲、ThreadLocal粪薛、Volatile這幾個(gè)并發(fā)基礎(chǔ)知識(shí)的底層原理。這一節(jié)搏恤,你可以跟我一起深入了解下synchronized關(guān)鍵字的底層原理和其涉及的基礎(chǔ)知識(shí)违寿。看完這篇成長(zhǎng)記熟空,你可以獲取到如下幾點(diǎn):
synchronized預(yù)備知識(shí):
- 理解什么是CAS藤巢?
- synchronized會(huì)形成幾種鎖的類(lèi)型
- HotspotJVM虛擬機(jī)Java對(duì)象內(nèi)存中的布局結(jié)構(gòu)是什么,markword是鎖的關(guān)鍵字段息罗?
- 操作系統(tǒng)中用戶(hù)態(tài)和內(nèi)核態(tài)的資源操作和進(jìn)程是什么意思掂咒?
synchronized核心流程及原理:
- 從3個(gè)層面初步分析sychronized的核心流程和原理
好了,讓我們一起開(kāi)始吧迈喉!
HelloSychronized
<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">HelloSychronized</span></h3></div>
我們來(lái)寫(xiě)一個(gè)多線程i++的程序绍刮,體驗(yàn)一下,多線程如果是并發(fā)的修改一個(gè)數(shù)據(jù)挨摸,會(huì)有什么樣的線程并發(fā)安全問(wèn)題孩革。
剛才說(shuō)過(guò)了,volatile得运,解決的對(duì)一個(gè)共享數(shù)據(jù)膝蜈,有人寫(xiě),有人讀澈圈,多個(gè)線程并發(fā)讀和寫(xiě)的可見(jiàn)性的問(wèn)題,而多個(gè)線程對(duì)一個(gè)共享數(shù)據(jù)并發(fā)的寫(xiě)帆啃,可能會(huì)導(dǎo)致數(shù)據(jù)出錯(cuò)瞬女,產(chǎn)生原子性的問(wèn)題。
volatile為什么不能保證原子性努潘? 從JMM內(nèi)存模型就可以看出來(lái)诽偷,多個(gè)線程同時(shí)修改一個(gè)變量,都是在自己本地內(nèi)存中修改疯坤,volatile只是保證一個(gè)線程修改报慕,另一個(gè)線程讀的時(shí)候,發(fā)起修改的線程是強(qiáng)制刷新數(shù)據(jù)主存压怠,過(guò)期其他線程的工作內(nèi)存的緩存眠冈,沒(méi)法做到多個(gè)線程在本地內(nèi)存同時(shí)寫(xiě)的時(shí)候,限制只能有一個(gè)線程修改,因?yàn)榫€程自己修改自己內(nèi)存的數(shù)據(jù)沒(méi)有發(fā)生競(jìng)爭(zhēng)關(guān)系蜗顽。而且之后會(huì)給各自寫(xiě)入主內(nèi)存布卡,當(dāng)然就保證不了只能有一個(gè)線程修改主內(nèi)存的數(shù)據(jù),做不到原子性了雇盖。
為了解決這個(gè)問(wèn)題忿等,可以使用syncrhonized給修改這個(gè)操作加一把鎖,一旦說(shuō)某個(gè)線程加了一把鎖之后崔挖,就會(huì)保證贸街,其他的線程沒(méi)法去讀取和修改這個(gè)變量的值了,同一時(shí)間狸相,只有一個(gè)線程可以讀這個(gè)數(shù)據(jù)以及修改這個(gè)數(shù)據(jù)薛匪,別的線程都會(huì)卡在嘗試獲取鎖那兒。這樣也就不會(huì)出現(xiàn)并發(fā)同時(shí)修改卷哩,數(shù)據(jù)出錯(cuò)蛋辈,原子性問(wèn)題了。
synchronized鎖一般有兩類(lèi)将谊,一種是對(duì)某個(gè)實(shí)例對(duì)象來(lái)加鎖冷溶,另外一種是對(duì)這個(gè)類(lèi)進(jìn)行加鎖。相信大家很熟悉了尊浓,這里用一個(gè)Hello synchronized的小例子逞频,舉一個(gè)簡(jiǎn)單對(duì)象加鎖的例子。
代碼如下:
public class HelloSynchronized {
public static void main(String[] args) {
Object o = new Object();
synchronized (o){
}
}
}
對(duì)類(lèi)加鎖和對(duì)實(shí)例對(duì)象的更多例子這里就不舉例了栋齿,我們更多的是研究synchronized它的底層原理苗胀。基本的使用相信你一定可以自己學(xué)習(xí)到瓦堵。
在分析sychronized原理期間基协,需要不斷的補(bǔ)充一些基礎(chǔ)知識(shí)。
學(xué)習(xí)sychronized先決條件(Prerequisites)
<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">學(xué)習(xí)sychronized先決條件(Prerequisites)</span></h3></div>
- sychronized鎖的概念
在JDK 早期 sychronized 使用的時(shí)候菇用,直接創(chuàng)建的重量級(jí)鎖澜驮,性能很不好。
在之后JDK新的版本中惋鸥,sychronized優(yōu)化了鎖杂穷,分為了4種,無(wú)鎖態(tài)卦绣、偏向鎖耐量、自旋鎖(輕量鎖)、重量鎖滤港,會(huì)根據(jù)情況自動(dòng)升級(jí)鎖廊蜒。
這四種鎖分別表示什么意思呢?
無(wú)鎖態(tài)表示第一次對(duì)剛創(chuàng)建的對(duì)象或者類(lèi)加鎖時(shí)的狀態(tài)。我發(fā)現(xiàn)只有一個(gè)線程在操作代碼塊的資源劲藐,壓根不需要加鎖八堡。此時(shí)會(huì)處于無(wú)鎖態(tài)。
偏向鎖聘芜,類(lèi)似于貼標(biāo)簽兄渺,表示這個(gè)資源暫時(shí)屬于某個(gè)線程,偏向它所有了汰现。打個(gè)比方挂谍,就好比一個(gè)座位只能做一個(gè)人,你坐下后瞎饲,在座位上貼上了你自己的標(biāo)簽口叙。別人發(fā)現(xiàn)已經(jīng)有標(biāo)簽了,肯定就不會(huì)在坐了嗅战。
輕量鎖(自旋鎖):輕量鎖妄田,底層是CAS自旋的操作,所以也叫自旋鎖驮捍。這里簡(jiǎn)單普及下自旋CAS的操作流程疟呐,之后將Aotmic類(lèi)的時(shí)候會(huì)仔細(xì)講下。CAS自旋流程如下:
最后我們來(lái)聊下什么是重量級(jí)鎖东且?這又要牽扯另一個(gè)知識(shí)了启具。在Linux操作系統(tǒng)層面,由于需要限制不同的程序之間的訪問(wèn)能力, 防止他們獲取別的程序的內(nèi)存數(shù)據(jù), 或者獲取外圍設(shè)備的數(shù)據(jù), 并發(fā)送到網(wǎng)絡(luò), CPU劃分出兩個(gè)權(quán)限等級(jí)用戶(hù)態(tài)和內(nèi)核態(tài)珊泳。用于表示進(jìn)程運(yùn)行時(shí)所處狀態(tài)鲁冯。
你可以簡(jiǎn)單理解,一個(gè)程序啟動(dòng)后會(huì)有對(duì)應(yīng)的進(jìn)程色查,它們操作的資源分為兩種薯演,屬于用戶(hù)態(tài)的資源或者內(nèi)核態(tài)的資源。
用戶(hù)態(tài)是不能直接操作內(nèi)核態(tài)中資源的秧了,只能通知內(nèi)核態(tài)來(lái)操作跨扮。這個(gè)在硬件級(jí)別也有對(duì)應(yīng)的指令級(jí)別(比如Intel ring0-ring3級(jí)別的指令,ring0級(jí)別一般對(duì)應(yīng)的就是用戶(hù)態(tài)進(jìn)程可以操作的指令示惊,ring3對(duì)應(yīng)的是內(nèi)核態(tài)進(jìn)程可以發(fā)起的指令)好港。
如下圖所示:
這個(gè)和synchronized有什么關(guān)系呢愉镰?因?yàn)閟ynchronized加重量級(jí)鎖的操作米罚,是對(duì)硬件資源的鎖指令操作,所以肯定是需要處于內(nèi)核態(tài)的進(jìn)程才可以操作丈探,JVM的進(jìn)程只是處于用戶(hù)態(tài)的進(jìn)程录择,所以需要向操作系統(tǒng)申請(qǐng),這個(gè)過(guò)程肯定會(huì)很消耗資源的。
比如隘竭,synchronized的本質(zhì)是JVM用戶(hù)空間的一個(gè)進(jìn)程(處于用戶(hù)態(tài))向操作系統(tǒng)(內(nèi)核態(tài))發(fā)起一個(gè)lock的鎖指令操作 塘秦。
C++代碼如下:
//Adding a lock prefix to an instruction on MP machine
\#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; local; 1 : "
如下圖右邊所示:
- sychronized鎖狀態(tài)的記錄
了解了sychronized的鎖的幾種類(lèi)型后,怎么標(biāo)識(shí)是什么樣的synchronized鎖呢动看?這個(gè)就要聊到Java的對(duì)象在JVM的內(nèi)存中的結(jié)構(gòu)了尊剔。不同虛擬機(jī)結(jié)構(gòu)略有差別,這里講一下HotSpot虛擬機(jī)中的對(duì)象結(jié)構(gòu):
synchronized鎖狀態(tài)的信息就記錄在markword中菱皆。markword在64位的操作系統(tǒng)上须误,8字節(jié),64位大小的空間的區(qū)域仇轻。
不同的鎖的標(biāo)記在如下圖所示:
這個(gè)表你不用背下來(lái)京痢,你只要知道,synchronized的輕量鎖和重量鎖通過(guò)2位即可以區(qū)分出來(lái)篷店,偏向鎖和無(wú)鎖需要3位祭椰。
有了上面的基礎(chǔ)知識(shí)后,就可以開(kāi)始研究synchronized的底層原理了疲陕。
字節(jié)碼層面的synchronized
<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">synchronized</span></h3></div>
sychronized在Java代碼層面就如上面Hello Synconized那個(gè)最簡(jiǎn)單的例子所示方淤,我們來(lái)看下它的字節(jié)碼層面是什么樣子的?
上面main方法的字節(jié)碼如下:
0 new #2 <java/lang/Object>
3 dup
4 invokespecial #1 <java/lang/Object.<init>>
7 astore_1
8 aload_1
9 dup
10 astore_2
11 monitorenter
12 aload_2
13 monitorexit
14 goto 22 (+8)
17 astore_3
18 aload_2
19 monitorexit
20 aload_3
21 athrow
22 return
new鸭轮、dup臣淤、invokespecial、 astore_1這些指令是學(xué)習(xí)volatile的時(shí)候你應(yīng)該很熟悉了窃爷。我這里需要關(guān)注的是另外 2個(gè)核心的JVM指令:monitorenter邑蒋、monitorexit。
這個(gè)表示sychronized加鎖的同步代碼塊的進(jìn)入和退出按厘。為什么有兩個(gè)monitorexit呢医吊?一個(gè)是正常退出,一個(gè)拋出異常也會(huì)退出釋放鎖逮京。
JVM層面的synchronized
<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">JVM層面的synchronized</span></h3></div>
那么卿堂,當(dāng) JVM的HotSpot實(shí)現(xiàn)中,當(dāng)遇到這兩個(gè)JVM指令懒棉,又是如何執(zhí)行的呢草描?讓我們來(lái)看一下。
在JVM HotSpot的C++代碼實(shí)際執(zhí)行過(guò)程中策严,執(zhí)行了一個(gè)InterpreterRuntime:: monitorenter方法穗慕,代碼如下:
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
\#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
\#endif
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
"must be NULL or an object");
\#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
\#endif
IRT_END
你可以看下上面的方法的脈絡(luò)(不懂C++也沒(méi)有關(guān)系,懂if-else就行)妻导。它的核心有兩個(gè)if逛绵。
第一個(gè)if根據(jù)變量名字PrintBiasedLockingStatistics可以判斷出應(yīng)該是打印偏向鎖的統(tǒng)計(jì)信息怀各,明顯不是最重要的。
第二個(gè)if同理术浪,UseBiasedLocking表示了是否使用了偏向鎖瓢对,如果是調(diào)用了ObjectSynchronizer::fast_enter否則
ObjectSynchronizer::slow_enter。
很明顯胰苏,第二個(gè)if中是synchronized加鎖的核心代碼硕蛹。我們還需要繼續(xù)看下它們的脈絡(luò)。
代碼如下:synchronizer.cpp
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
if (UseBiasedLocking) {
if (!SafepointSynchronize::is_at_safepoint()) {
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
return;
}
} else {
assert(!attempt_rebias, "can not rebias toward VM thread");
BiasedLocking::revoke_at_safepoint(obj);
}
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
slow_enter (obj, lock, THREAD) ;
}
可以看到fast_enter方法硕并,核心脈絡(luò)除了取消偏向和重新偏向的邏輯(從變量明和注釋可以看出來(lái)妓美,這里暫時(shí)不重要,先忽略)鲤孵,最后核心脈絡(luò)還是調(diào)用了slow_enter方法壶栋。讓我們來(lái)看下:
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
if (mark->is_neutral()) {
// Anticipate successful CAS -- the ST of the displaced mark must
// be visible <= the ST performed by the CAS.
lock->set_displaced_header(mark);
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
return ;
}
// Fall through to inflate() ...
} else
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");
lock->set_displaced_header(NULL);
return;
}
\#if 0
// The following optimization isn't particularly useful.
if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {
lock->set_displaced_header (NULL) ;
return ;
}
\#endif
// The object header will never be displaced to this lock,
// so it does not matter what the value is, except that it
// must be non-zero to avoid looking like a re-entrant lock,
// and must not look locked either.
lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
上面這一段是sychronized加鎖,核心中的核心普监,可以發(fā)現(xiàn)很多有意思的地方:
1) 從注釋可以看出贵试,鎖會(huì)有膨脹過(guò)程,對(duì)象頭會(huì)記錄鎖的相關(guān)信息凯正。
2) Atomic::cmpxchg_ptr體現(xiàn)了ompare and exchange (CAS)操作毙玻,是輕量級(jí)鎖。
3) mark->has_locker() && THREAD->is_lock_owned((address)mark->locker()體現(xiàn)了synchronized是可重入鎖
4) 最后的ObjectSynchronizer::inflate意思為膨脹為重量級(jí)鎖廊散。
C++的代碼有很多細(xì)節(jié)和知識(shí)桑滩,你開(kāi)始學(xué)習(xí)的時(shí)候不要想著全部搞清楚,一定要有之前學(xué)到的思想允睹,先脈絡(luò)后細(xì)節(jié)运准。搞清楚脈絡(luò)再說(shuō)研究細(xì)節(jié)的部分。
所以缭受,通過(guò)初步看過(guò)synchronized的HotSpot C++代碼實(shí)現(xiàn)胁澳,重點(diǎn)的脈絡(luò)就是鎖升級(jí)的過(guò)程和原理,接下來(lái)重點(diǎn)分析一下這個(gè)過(guò)程米者。
synchronized鎖升級(jí)的過(guò)程
<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">synchronized鎖升級(jí)的過(guò)程</span></h3></div>
前面通過(guò)從字節(jié)碼層面到JVM層面初步了解了synchronized的實(shí)現(xiàn)韭畸,結(jié)合之前說(shuō)的sychronized的鎖的幾種類(lèi)型。最終可以分析出來(lái)synchronized鎖會(huì)有一個(gè)升級(jí)的過(guò)程蔓搞。過(guò)程如下圖所示:
這個(gè)圖非常重要胰丁,大家一定要牢記住。下一節(jié)會(huì)花費(fèi)整整一節(jié)來(lái)講在這個(gè)圖喂分。
本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布锦庸!