大家好,我是王有志。關(guān)注王有志,一起聊技術(shù),聊游戲,從北漂生活談到國際風云。
網(wǎng)站建設(shè)哪家好,找成都創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、微信小程序開發(fā)、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了增城免費建站歡迎大家使用!鴿了這么久是給自己找到了冠冕堂皇的理由–羊了。說實話發(fā)燒那幾天真的很難受,根本不想下床,完成日常工作都已經(jīng)用盡了全部的力氣,根本沒經(jīng)歷寫文章。
言歸正傳,今天我們繼續(xù)學習synchronized
的升級過程,目前只剩下最后一步了:輕量級鎖->重量級鎖。
通過今天的內(nèi)容,希望能幫助大家解答synchronized都問啥?中除鎖粗化,鎖消除以及Java 8對synchronized
的優(yōu)化外全部的問題。
從源碼揭秘偏向鎖的升級 最后,看到synchronizer#slow_enter如果存在競爭,會調(diào)用ObjectSynchronizer::inflate
方法,進行輕量級鎖的升級(膨脹)。
Tips:
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
......
ObjectSynchronizer::inflate(THREAD, obj(), inflate_cause_monitor_enter)->enter(THREAD);
}
通過ObjectSynchronizer::inflate
獲取重量級鎖ObjectMonitor,然后執(zhí)行ObjectMonitor::enter
方法。
Tips:
ObjectSynchronizer::inflate
,因此代碼分析放在重量級鎖源碼分析中。了解ObjectMonitor::enter
的邏輯前,先來看ObjectMonitor的結(jié)構(gòu):
class ObjectMonitor {
private:
// 保存與ObjectMonitor關(guān)聯(lián)Object的markOop
volatile markOop _header;
// 與ObjectMonitor關(guān)聯(lián)的Object
void* volatile _object;
protected:
// ObjectMonitor的擁有者
void * volatile _owner;
// 遞歸計數(shù)
volatile intptr_t _recursions;
// 等待線程隊列,cxq移入/Object.notify喚醒的線程
ObjectWaiter * volatile _EntryList;
private:
// 競爭隊列
ObjectWaiter * volatile _cxq;
// ObjectMonitor的維護線程
Thread * volatile _Responsible;
protected:
// 線程掛起隊列(調(diào)用Object.wait)
ObjectWaiter * volatile _WaitSet;
}
_header
字段存儲了Object的markOop,為什么要這樣?因為鎖升級后沒有空間存儲Object的markOop了,存儲到_header中是為了在退出時能夠恢復(fù)到加鎖前的狀態(tài)。
Tips:
EntryList
中等待線程來自于cxq
移入,或Object.notify
喚醒但未執(zhí)行。objectMonito#enter方法可以拆成三個部分,首先是競爭成功或重入的場景:
// 獲取當前線程Self
Thread * const Self = THREAD;
// CAS搶占鎖,如果失敗則返回_owner
void * cur = Atomic::cmpxchg(Self, &_owner, (void*)NULL);
if (cur == NULL) {
// CAS搶占鎖成功直接返回
return;
}
// CAS失敗場景
// 重量級鎖重入
if (cur == Self) {
// 遞歸計數(shù)+1
_recursions++;
return;
}
// 當前線程是否曾持有輕量級鎖
// 可以看做是特殊的重入
if (Self->is_lock_owned ((address)cur)) {
// 遞歸計數(shù)器置為1
_recursions = 1;
_owner = Self;
return;
}
重入和升級的場景中,都會操作_recursions
。_recursions
記錄了進入ObjectMonitor的次數(shù),解鎖時要經(jīng)歷相應(yīng)次數(shù)的退出操作才能完成解鎖。
以上都是成功獲取鎖的場景,那么產(chǎn)生競爭導(dǎo)致失敗的場景是怎樣的呢?來看適應(yīng)性自旋的部分,ObjectMonitor倒數(shù)第二次對“輕量”的追求:
// 嘗試自旋來競爭鎖
Self->_Stalled = intptr_t(this);
if (Knob_SpinEarly && TrySpin (Self) >0) {
Self->_Stalled = 0;
return;
}
objectMonitor#TrySpin方法是對適應(yīng)性自旋的支持。Java 1.6后加入,移除默認次數(shù)的自旋,將自旋次數(shù)的決定權(quán)交給JVM。
JVM根據(jù)鎖上一次自旋情況決定,如果剛剛自旋成功,并且持有鎖的線程正在執(zhí)行,JVM會允許再次嘗試自旋。如果該鎖的自旋經(jīng)常失敗,那么JVM會直接跳過自旋過程。
Tips:
到目前為止,無論是CAS還是自旋,都是偏向鎖和輕量級鎖中出現(xiàn)過的技術(shù),為什么會讓ObjectMonitor背上“重量級”的名聲呢?
最后是競爭失敗的場景:
// 此處省略了修改當前線程狀態(tài)的代碼
for (;;) {
EnterI(THREAD);
}
實際上,進入ObjectMonitor#EnterI后也是先嘗試“輕量級”的加鎖方式:
void ObjectMonitor::EnterI(TRAPS) {
if (TryLock (Self) >0) {
return;
}
if (TrySpin (Self) >0) {
return;
}
}
接來下是重量級的真正實現(xiàn):
// 將當前線程(Self)封裝為ObjectWaiter的node
ObjectWaiter node(Self);
Self->_ParkEvent->reset();
node._prev = (ObjectWaiter *) 0xBAD;
node.TState = ObjectWaiter::TS_CXQ;
// 將node插入到cxq的頭部
ObjectWaiter * nxt;
for (;;) {
node._next = nxt = _cxq;
if (Atomic::cmpxchg(&node, &_cxq, nxt) == nxt)
break;
// 為了減少插入到cxq頭部的次數(shù),試試能否直接獲取到鎖
if (TryLock (Self) >0) {
return;
}
}
邏輯一目了然,封裝ObjectWaiter對象,并加入到cxq
隊列頭部。接著往下執(zhí)行:
// 將當前線程(Self)設(shè)置為當前ObjectMonitor的維護線程(_Responsible)
// SyncFlags的默認值為0,可以通過-XX:SyncFlags設(shè)置
if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
Atomic::replace_if_null(Self, &_Responsible);
}
for (;;) {
// 嘗試設(shè)置_Responsible
if ((SyncFlags & 2) && _Responsible == NULL) {
Atomic::replace_if_null(Self, &_Responsible);
}
// park當前線程
if (_Responsible == Self || (SyncFlags & 1)) {
Self->_ParkEvent->park((jlong) recheckInterval);
// 簡單的退避算法,recheckInterval從1ms開始
recheckInterval *= 8;
if (recheckInterval >MAX_RECHECK_INTERVAL) {
recheckInterval = MAX_RECHECK_INTERVAL;
}
} else {
Self->_ParkEvent->park();
}
// 嘗試獲取鎖
if (TryLock(Self) >0)
break;
if ((Knob_SpinAfterFutile & 1) && TrySpin(Self) >0)
break;
if (_succ == Self)
_succ = NULL;
}
邏輯也不復(fù)雜,不斷的park
當前線程,被喚醒后嘗試獲取鎖。需要關(guān)注-XX:SyncFlags
的設(shè)置:
SyncFlags == 0
時,synchronized
直接掛起線程;SyncFlags == 1
時,synchronized
將線程掛起指定時間。前者是永久掛起,需要被其它線程喚醒,而后者掛起指定的時間后自動喚醒。
Tips:關(guān)于線程你必須知道的8個問題(中)聊到過park
和parkEvent
,底層是通過pthread_cond_wait
和pthread_cond_timedwait
實現(xiàn)的。
釋放重量級鎖的源碼和注釋非常長,我們省略大部分內(nèi)容,只看關(guān)鍵部分。
重入鎖退出我們知道,重入是不斷增加_recursions
的計數(shù),那么退出重入的場景就非常簡單了:
void ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * const Self = THREAD;
// 第二次持有鎖時,_recursions == 1
// 重入場景只需要退出重入即可
if (_recursions != 0) {
_recursions--;
return;
}
.....
}
不斷的減少_recursions
的計數(shù)。
JVM的實現(xiàn)中,當前線程是鎖的持有者且沒有重入時,首先會釋放自己持有的鎖,接著將改動寫入到內(nèi)存中,最后還肩負著喚醒下一個線程的責任。先來看釋放和寫入內(nèi)存的邏輯:
// 置空鎖的持有者
OrderAccess::release_store(&_owner, (void*)NULL);
// storeload屏障,
OrderAccess::storeload();
// 沒有競爭線程則直接退出
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
TEVENT(Inflated exit - simple egress);
return;
}
storeload
屏障,對于如下語句:
store1;
storeLoad;
load2
保證store1
指令的寫入在load2
指令執(zhí)行前,對所有處理器可見。
Tips:volatile
中詳細解釋內(nèi)存屏障。
執(zhí)行釋放鎖和寫入內(nèi)存后,只需要喚醒下一個線程來“交接”鎖的使用權(quán)。但是有兩個“等待隊列”:cxq
和EntryList
,該從哪個開始喚醒呢?
Java 11前,根據(jù)QMode
來選擇不同的策略:
QMode == 0
,默認策略,將cxq
放入EntryList
;QMode == 1
,翻轉(zhuǎn)cxq
,并放入EntryList
;QMode == 2
,直接從cxq
中喚醒;QMode == 3
,將cxq
移入到EntryList
的尾部;QMode == 4
,將cxq
移入到EntryList
的頭部。不同的策略導(dǎo)致了不同的喚醒順序,現(xiàn)在你知道為什么說synchronized
是非公平鎖了吧?
objectMonitor#ExitEpilog方法就很簡單了,調(diào)用的是與park
對應(yīng)的unpark
方法,這里就不多說了。
Tips:Java 12的objectMonitor移除了QMode
,也就是說只有一種喚醒策略了。
我們對重量級鎖做個總結(jié)。synchronized
的重量級鎖是ObjectMonitor
,它使用到的關(guān)鍵技術(shù)有CAS和park。相較于mutex#Monitor來說,它們的本質(zhì)相同,對park的封裝,但ObjectMonitor
是做了大量優(yōu)化的復(fù)雜實現(xiàn)。
我們看到了重量級鎖是如何實現(xiàn)重入性的,以及喚醒策略導(dǎo)致的“不公平”。那么我們常說的synchronized
保證了原子性,有序性和可見性,是如何實現(xiàn)的呢?
大家可以先思考下這個問題,下篇文章會做一個全方位的總結(jié),給synchronized
收下尾。
好了,今天就到這里了,Bye~~
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
本文題目:09.什么是synchronized的重量級鎖?-創(chuàng)新互聯(lián)
瀏覽路徑:http://jinyejixie.com/article48/dehohp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站營銷、動態(tài)網(wǎng)站、軟件開發(fā)、ChatGPT、網(wǎng)頁設(shè)計公司、定制網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容