Sychronized關(guān)鍵字使用場景
三種方法
修飾實例方法
作用于當前對象實例加鎖,進入同步代碼前要獲得當前對象實例的鎖。
synchronized void method() {
//業(yè)務代碼
}
修飾靜態(tài)方法
給當前類加鎖,會作用于類的所有對象實例 ,進入同步代碼前要獲得 當前 class 的鎖。因為靜態(tài)成員不屬于任何一個實例對象,是類成員( static 表明這是該類的一個靜態(tài)資源,不管 new 了多少個對象,只有一份)。
所以,如果一個線程 A 調(diào)用一個實例對象的非靜態(tài) synchronized 方法,而線程 B 需要調(diào)用這個實例對象所屬類的靜態(tài) synchronized 方法,是允許的,不會發(fā)生互斥現(xiàn)象,因為訪問靜態(tài) synchronized 方法占用的鎖是當前類的鎖,而訪問非靜態(tài) synchronized 方法占用的鎖是當前實例對象鎖。
synchronized static void method() {
//業(yè)務代碼
}
修飾代碼塊
指定加鎖對象,對給定對象/類加鎖。synchronized(this|object) 表示進入同步代碼庫前要獲得給定對象的鎖。synchronized(類.class) 表示進入同步代碼前要獲得 當前 class 的鎖。
synchronized(this) {
//業(yè)務代碼
}
總結(jié)
- synchronized 關(guān)鍵字加到 static 靜態(tài)方法和 synchronized(class) 代碼塊上都是是給 Class 類上鎖。
- synchronized 關(guān)鍵字加到實例方法上是給對象實例上鎖。
- 盡量不要使用 synchronized(String a) 因為 JVM 中,字符串常量池具有緩存功能!
Sychroniezd 關(guān)鍵字實現(xiàn)原理
對象頭
當線程訪問同步塊時首先需要獲得鎖并把相關(guān)信息存儲在對象頭中。所以 wait、notify、notifyAll 這些方法為什么被設計在 Object 中或許你已經(jīng)找到答案了。
Hotspot 有兩種對象頭:
- 數(shù)組類型,使用 arrayOopDesc 來描述對象頭
- 其它,使用 instanceOopDesc 來描述對象頭
對象頭由兩部分組成:
- Mark Word:存儲自身的運行時數(shù)據(jù),例如 HashCode、GC 年齡、鎖相關(guān)信息等內(nèi)容。
- Klass Pointer:類型指針指向它的類元數(shù)據(jù)的指針。
64 位虛擬機 Mark Word 是 64bit 其結(jié)構(gòu)如下:

在 JDK 6 中虛擬機團隊對鎖進行了重要改進,優(yōu)化了其性能引入了 偏向鎖、輕量級鎖、適應性自旋、鎖消除、鎖粗化等實現(xiàn)。
總體上來說鎖狀態(tài)升級流程如下:

偏向鎖
流程
HotSpot的作者經(jīng)過研究發(fā)現(xiàn),大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價更低而引入了偏向鎖。當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖。如果測試成功,表示線程已經(jīng)獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。
偏向鎖的撤銷
偏向鎖使用了一種等到競爭出現(xiàn)才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有正在執(zhí)行的字節(jié)碼)。它會首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著,如果線程不處于活動狀態(tài),則將對象頭設置成無鎖狀態(tài);如果線程仍然活著,擁有偏向鎖的棧會被執(zhí)行,遍歷偏向?qū)ο蟮逆i記錄,棧中的鎖記錄和對象頭的Mark Word要么重新偏向于其他線程,要么恢復到無鎖或者標記對象不適合作為偏向鎖,最后喚醒暫停的線程。

關(guān)閉偏向鎖
偏向鎖在Java 6和Java 7里是默認啟用的,但是它在應用程序啟動幾秒鐘之后才激活,如有必要可以使用JVM參數(shù)來關(guān)閉延遲:-XX:BiasedLockingStartupDelay=0。如果你確定應用程序里所有的鎖通常情況下處于競爭狀態(tài),可以通過JVM參數(shù)關(guān)閉偏向鎖:-XX:-UseBiasedLocking=false,那么程序默認會進入輕量級鎖狀態(tài)。
輕量級鎖
輕量級加鎖
線程在執(zhí)行同步塊之前,JVM會先在當前線程的棧楨中創(chuàng)建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displaced MarkWord。
拷貝成功后,虛擬機將使用CAS操作嘗試將對象的Mark Word更新為指向Lock Record的指針,并將Lock Record里的owner指針指向?qū)ο蟮腗ark Word。
如果這個更新動作成功了,那么這個線程就擁有了該對象的鎖,并且對象Mark Word的鎖標志位設置為“00”,表示此對象處于輕量級鎖定狀態(tài)。
如果輕量級鎖的更新操作失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是就說明當前線程已經(jīng)擁有了這個對象的鎖,那就可以直接進入同步塊繼續(xù)執(zhí)行,否則說明多個線程競爭鎖。
若當前只有一個等待線程,則該線程通過自旋進行等待。但是當自旋超過一定的次數(shù),或者一個線程在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖升級為重量級鎖。
輕量級解鎖
輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到對象頭,如果成功,則表示沒有競爭發(fā)生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。圖2-2是兩個線程同時爭奪鎖,導致鎖膨脹的流程圖。

因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態(tài)。當鎖處于這個狀態(tài)下,其他線程試圖獲取鎖時,都會被阻塞住,當持有鎖的線程釋放鎖之后會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖之爭。
自旋鎖 vs 適應性自旋鎖
阻塞或喚醒一個Java線程需要操作系統(tǒng)切換CPU狀態(tài)來完成,這種狀態(tài)轉(zhuǎn)換需要耗費處理器時間。如果同步代碼塊中的內(nèi)容過于簡單,狀態(tài)轉(zhuǎn)換消耗的時間有可能比用戶代碼執(zhí)行的時間還要長。
在許多場景中,同步資源的鎖定時間很短,為了這一小段時間去切換線程,線程掛起和恢復現(xiàn)場的花費可能會讓系統(tǒng)得不償失。如果物理機器有多個處理器,能夠讓兩個或以上的線程同時并行執(zhí)行,我們就可以讓后面那個請求鎖的線程不放棄CPU的執(zhí)行時間,看看持有鎖的線程是否很快就會釋放鎖。
而為了讓當前線程“稍等一下”,我們需讓當前線程進行自旋,如果在自旋完成后前面鎖定同步資源的線程已經(jīng)釋放了鎖,那么當前線程就可以不必阻塞而是直接獲取同步資源,從而避免切換線程的開銷。這就是自旋鎖。

自旋鎖本身是有缺點的,它不能代替阻塞。自旋等待雖然避免了線程切換的開銷,但它要占用處理器時間。如果鎖被占用的時間很短,自旋等待的效果就會非常好。反之,如果鎖被占用的時間很長,那么自旋的線程只會白浪費處理器資源。所以,自旋等待的時間必須要有一定的限度,如果自旋超過了限定次數(shù)(默認是10次,可以使用-XX:PreBlockSpin來更改)沒有成功獲得鎖,就應當掛起線程。
自旋鎖在JDK1.4.2中引入,使用-XX:+UseSpinning來開啟。JDK 6中變?yōu)槟J開啟,并且引入了自適應的自旋鎖(適應性自旋鎖)。
自適應意味著自旋的時間(次數(shù))不再固定,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)來決定。如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運行中,那么虛擬機就會認為這次自旋也是很有可能再次成功,進而它將允許自旋等待持續(xù)相對更長的時間。如果對于某個鎖,自旋很少成功獲得過,那在以后嘗試獲取這個鎖時將可能省略掉自旋過程,直接阻塞線程,避免浪費處理器資源。
重量級鎖
Monitor可以理解為一個同步工具或一種同步機制,通常被描述為一個對象。每一個Java對象就有一把看不見的鎖,稱為內(nèi)部鎖或者Monitor鎖。
Monitor是線程私有的數(shù)據(jù)結(jié)構(gòu),每一個線程都有一個可用monitor record列表,同時還有一個全局的可用列表。每一個被鎖住的對象都會和一個monitor關(guān)聯(lián),同時monitor中有一個Owner字段存放擁有該鎖的線程的唯一標識,表示該鎖被這個線程占用。
現(xiàn)在話題回到synchronized,synchronized通過Monitor來實現(xiàn)線程同步,Monitor是依賴于底層的操作系統(tǒng)的Mutex Lock(互斥鎖)來實現(xiàn)的線程同步。
如同我們在自旋鎖中提到的“阻塞或喚醒一個Java線程需要操作系統(tǒng)切換CPU狀態(tài)來完成,這種狀態(tài)轉(zhuǎn)換需要耗費處理器時間。如果同步代碼塊中的內(nèi)容過于簡單,狀態(tài)轉(zhuǎn)換消耗的時間有可能比用戶代碼執(zhí)行的時間還要長”。這種方式就是synchronized最初實現(xiàn)同步的方式,這就是JDK 6之前synchronized效率低的原因。這種依賴于操作系統(tǒng)Mutex Lock所實現(xiàn)的鎖我們稱之為“重量級鎖”,JDK 6中為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”。
重量級鎖中隊列
ObjectMonitor 中包含一個同步隊列(由 _cxq 和 _EntryList 組成)一個等待隊列( _WaitSet )。
被notify或 notifyAll 喚醒時根據(jù) policy 策略選擇加入的隊列(policy 默認為 0)
退出同步塊時根據(jù) QMode 策略來喚醒下一個線程(QMode 默認為 0)。
synchronized 的 monitor鎖機制和 JDK 并發(fā)包中的 AQS 是很相似的,只不過 AQS 中是一個同步隊列多個等待隊列。熟悉 AQS 的同學可以拿來做個對比。
隊列協(xié)作流程圖

源碼解析
在 HotSpot 中 monitor 是由 ObjectMonitor 實現(xiàn)的。其源碼是用 c++來實現(xiàn)的源文件是 ObjectMonitor.hpp 主要數(shù)據(jù)結(jié)構(gòu)如下所示:
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0, // 等待中的線程數(shù)
_recursions = 0; // 線程重入次數(shù)
_object = NULL; // 存儲該 monitor 的對象
_owner = NULL; // 指向擁有該 monitor 的線程
_WaitSet = NULL; // 等待線程 雙向循環(huán)鏈表_WaitSet 指向第一個節(jié)點
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; // 多線程競爭鎖時的單向鏈表
FreeNext = NULL ;
_EntryList = NULL ; // _owner 從該雙向循環(huán)鏈表中喚醒線程,
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0; // 前一個擁有此監(jiān)視器的線程 ID
}
- 初始時為 NULL。當有線程占有該 monitor 時 owner 標記為該線程的 ID。當線程釋放 monitor 時 owner 恢復為 NULL。owner 是一個臨界資源 JVM 是通過 CAS 操作來保證其線程安全的。
- _cxq:競爭隊列所有請求鎖的線程首先會被放在這個隊列中(單向)。_cxq 是一個臨界資源 JVM 通過 CAS 原子指令來修改_cxq 隊列。
每當有新來的節(jié)點入隊,它的 next 指針總是指向之前隊列的頭節(jié)點,而_cxq 指針會指向該新入隊的節(jié)點,所以是后來居上。 - _EntryList: _cxq 隊列中有資格成為候選資源的線程會被移動到該隊列中。
- _WaitSet: 等待隊列因為調(diào)用 wait 方法而被阻塞的線程會被放在該隊列中。
monitor競爭操作
- 通過 CAS 嘗試把 monitor 的 owner 字段設置為當前線程。
- 如果設置之前的 owner 指向當前線程,說明當前線程再次進入 monitor,即 重入鎖執(zhí)行 recursions ++ , 記錄重入的次數(shù)。
- 如果當前線程是第一次進入該 monitor, 設置 recursions 為 1,_owner 為當前線程,該線程成功獲得鎖并返回。
- 如果獲取鎖失敗,則等待鎖的釋放。
執(zhí)行 monitorenter 指令時 調(diào)用以下代碼:
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");
// 是否使用偏向鎖 JVM 啟動時設置的偏向鎖-XX:-UseBiasedLocking=false/true
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
slow_enter 方法主要是輕量級鎖的一些操作,如果操作失敗則會膨脹為重量級鎖,過程前面已經(jīng)描述比較清楚此處不在贅述。enter 方法則為重量級鎖的入口源碼如下:
void ATTR ObjectMonitor::enter(TRAPS) {
Thread * const Self = THREAD ;
void * cur ;
// 省略部分代碼
// 通過 CAS 操作嘗試把 monitor 的_owner 字段設置為當前線程
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (cur == NULL) {
assert (_recursions == 0 , "invariant") ;
assert (_owner == Self, "invariant") ;
return ;
}
// 線程重入,recursions++
if (cur == Self) {
_recursions ++ ;
return ;
}
// 如果當前線程是第一次進入該 monitor, 設置_recursions 為 1,_owner 為當前線程
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
_recursions = 1 ;
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
for (;;) {
jt->set_suspend_equivalent();
// 如果獲取鎖失敗,則等待鎖的釋放;
EnterI (THREAD) ;
if (!ExitSuspendEquivalent(jt)) break ;
_recursions = 0 ;
_succ = NULL ;
exit (false, Self) ;
jt->java_suspend_self();
}
Self->set_current_pending_monitor(NULL);
}
}
monitor等待
- 當前線程被封裝成 ObjectWaiter 對象 node,狀態(tài)設置成 ObjectWaiter::TS_CXQ。
- for 循環(huán)通過 CAS 把 node 節(jié)點 push 到_cxq列表中,同一時刻可能有多個線程把自己的 node 節(jié)點 push 到_cxq列表中。
- node 節(jié)點 push 到_cxq 列表之后,通過自旋嘗試獲取鎖,如果還是沒有獲取到鎖則通過 park 將當前線程掛起等待被喚醒。
- 當該線程被喚醒時會從掛起的點繼續(xù)執(zhí)行,通過 ObjectMonitor::TryLock 嘗試獲取鎖。
// 省略部分代碼
void ATTR ObjectMonitor::EnterI (TRAPS) {
Thread * Self = THREAD ;
assert (Self->is_Java_thread(), "invariant") ;
assert (((JavaThread *) Self)->thread_state() == _thread_blocked , "invariant") ;
// Try lock 嘗試獲取鎖
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
// 如果獲取成功則退出,避免 park unpark 系統(tǒng)調(diào)度的開銷
return ;
}
// 自旋獲取鎖
if (TrySpin(Self) > 0) {
assert (_owner == Self, "invariant");
assert (_succ != Self, "invariant");
assert (_Responsible != Self, "invariant");
return;
}
// 當前線程被封裝成 ObjectWaiter 對象 node, 狀態(tài)設置成 ObjectWaiter::TS_CXQ
ObjectWaiter node(Self) ;
Self->_ParkEvent->reset() ;
node._prev = (ObjectWaiter *) 0xBAD ;
node.TState = ObjectWaiter::TS_CXQ ;
// 通過 CAS 把 node 節(jié)點 push 到_cxq 列表中
ObjectWaiter * nxt ;
for (;;) {
// node節(jié)點插在頭部,置換為_cxq隊列的頭節(jié)點
node._next = nxt = _cxq ;
if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
// 再次 tryLock
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
}
for (;;) {
// 本段代碼的主要思想和 AQS 中相似可以類比來看
// 再次嘗試
if (TryLock (Self) > 0) break ;
assert (_owner != Self, "invariant") ;
if ((SyncFlags & 2) && _Responsible == NULL) {
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
// 滿足條件則 park self
if (_Responsible == Self || (SyncFlags & 1)) {
TEVENT (Inflated enter - park TIMED) ;
Self->_ParkEvent->park ((jlong) RecheckInterval) ;
// Increase the RecheckInterval, but clamp the value.
RecheckInterval *= 8 ;
if (RecheckInterval > 1000) RecheckInterval = 1000 ;
} else {
TEVENT (Inflated enter - park UNTIMED) ;
// 通過 park 將當前線程掛起,等待被喚醒
Self->_ParkEvent->park() ;
}
if (TryLock(Self) > 0) break ;
// 再次嘗試自旋
if ((Knob_SpinAfterFutile & 1) && TrySpin(Self) > 0) break;
}
return ;
}
monitor釋放
當某個持有鎖的線程執(zhí)行完同步代碼塊時,會釋放鎖并 unpark 后續(xù)線程(由于篇幅只保留重要代碼)。
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * Self = THREAD ;
if (_recursions != 0) {
_recursions--; // this is simple recursive enter
TEVENT (Inflated exit - recursive) ;
return ;
}
ObjectWaiter * w = NULL ;
int QMode = Knob_QMode ;
// 直接繞過 EntryList 隊列,從 cxq 隊列中獲取線程用于競爭鎖
if (QMode == 2 && _cxq != NULL) {
w = _cxq ;
assert (w != NULL, "invariant") ;
assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
ExitEpilog (Self, w) ;
return ;
}
// cxq 隊列插入 EntryList 尾部
if (QMode == 3 && _cxq != NULL) {
w = _cxq ;
// _cxq指向null
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
// 單向鏈表轉(zhuǎn)雙向鏈表
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
ObjectWaiter * Tail ;
for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
// 放到entryList列表尾部
if (Tail == NULL) {
_EntryList = w ;
} else {
Tail->_next = w ;
w->_prev = Tail ;
}
}
// cxq 隊列插入到_EntryList 頭部
if (QMode == 4 && _cxq != NULL) {
// 把 cxq 隊列放入 EntryList
// 此策略確保最近運行的線程位于 EntryList 的頭部
w = _cxq ;
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
assert (w != NULL , "invariant") ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
if (_EntryList != NULL) {
q->_next = _EntryList ;
_EntryList->_prev = q ;
}
_EntryList = w ;
}
w = _EntryList ;
if (w != NULL) {
assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
w = _cxq ;
if (w == NULL) continue ;
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
if (QMode == 1) {
// QMode == 1 : 把 cxq 傾倒入 EntryList 逆序
// 單向鏈表改雙向,且逆序。s代表前一個節(jié)點,u代表下一個節(jié)點
ObjectWaiter * s = NULL ;
ObjectWaiter * t = w ;
ObjectWaiter * u = NULL ;
while (t != NULL) {
guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
t->TState = ObjectWaiter::TS_ENTER ;
u = t->_next ;
t->_prev = u ;
t->_next = s ;
s = t;
t = u ;
}
_EntryList = s ;
assert (s != NULL, "invariant") ;
} else {
// QMode == 0 or QMode == 2
_EntryList = w ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
// 將單向鏈表構(gòu)造成雙向環(huán)形鏈表;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
}
if (_succ != NULL) continue;
w = _EntryList ;
if (w != NULL) {
guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
}
}
notify喚醒
notify 或者 notifyAll 方法可以喚醒同一個鎖監(jiān)視器下調(diào)用 wait 掛起的線程,具體實現(xiàn)如下:
void ObjectMonitor::notify(TRAPS) {
CHECK_OWNER();
if (_WaitSet == NULL) {
TEVENT (Empty - Notify);
return;
}
DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);
int Policy = Knob_MoveNotifyee;
Thread::SpinAcquire(&_WaitSetLock, "WaitSet - notify");
ObjectWaiter *iterator = DequeueWaiter();
if (iterator != NULL) {
// 省略一些代碼
// 頭插 EntryList
if (Policy == 0) {
if (List == NULL) {
iterator->_next = iterator->_prev = NULL;
_EntryList = iterator;
} else {
List->_prev = iterator;
iterator->_next = List;
iterator->_prev = NULL;
_EntryList = iterator;
}
} else if (Policy == 1) { // 尾插 EntryList
if (List == NULL) {
iterator->_next = iterator->_prev = NULL;
_EntryList = iterator;
} else {
ObjectWaiter *Tail;
for (Tail = List; Tail->_next != NULL; Tail = Tail->_next);
assert (Tail != NULL && Tail->_next == NULL, "invariant");
Tail->_next = iterator;
iterator->_prev = Tail;
iterator->_next = NULL;
}
} else if (Policy == 2) { // 頭插 cxq
// prepend to cxq
if (List == NULL) {
iterator->_next = iterator->_prev = NULL;
_EntryList = iterator;
} else {
iterator->TState = ObjectWaiter::TS_CXQ;
for (;;) {
ObjectWaiter *Front = _cxq;
iterator->_next = Front;
if (Atomic::cmpxchg_ptr(iterator, &_cxq, Front) == Front) {
break;
}
}
}
} else if (Policy == 3) { // 尾插 cxq
iterator->TState = ObjectWaiter::TS_CXQ;
for (;;) {
ObjectWaiter *Tail;
Tail = _cxq;
if (Tail == NULL) {
iterator->_next = NULL;
if (Atomic::cmpxchg_ptr(iterator, &_cxq, NULL) == NULL) {
break;
}
} else {
while (Tail->_next != NULL) Tail = Tail->_next;
Tail->_next = iterator;
iterator->_prev = Tail;
iterator->_next = NULL;
break;
}
}
} else {
ParkEvent *ev = iterator->_event;
iterator->TState = ObjectWaiter::TS_RUN;
OrderAccess::fence();
ev->unpark();
}
if (Policy < 4) {
iterator->wait_reenter_begin(this);
}
}
// 自旋釋放
Thread::SpinRelease(&_WaitSetLock);
if (iterator != NULL && ObjectMonitor::_sync_Notifications != NULL) {
ObjectMonitor::_sync_Notifications->inc();
}
}
總結(jié)
本文主要介紹了synchronized關(guān)鍵字的使用方式,鎖升級過程,以及相關(guān)源碼分析,源碼這塊主要介紹了同步隊列和等待隊列之間的轉(zhuǎn)移過程,同步隊列包括_cxq隊列和_entryList隊列,搶鎖失敗的線程會先放到_cxq隊列的頭部,在擁有鎖的線程釋放鎖時,會根據(jù)不同的QMode,選擇從_cxq直接取值,或者將_cxq頭插或尾插到_entryList隊列中,具體可以見隊列轉(zhuǎn)換圖。如果直接從_cxq取值的話,就會是后阻塞的線程先喚醒。
從整個過程來看,synchronized整體加鎖的思路和對隊列的處理與aqs都比較類似,下面貼下兩者的相同與區(qū)別:
