死磕Synchronized底層實現--重量級鎖
本文為死磕Synchronized底層實現第三篇文章,內容為重量級鎖實現。
本系列文章將對HotSpot的 synchronized
鎖實現進行全面分析,內容包括偏向鎖、輕量級鎖、重量級鎖的加鎖、解鎖、鎖升級流程的原理及原始碼分析,希望給在研究 synchronized
路上的同學一些幫助。主要包括以下幾篇文章:
ofollow,noindex">死磕Synchronized底層實現--概論
更多文章見個人部落格: https://github.com/farmerjohngit/myblog
重量級的膨脹和加鎖流程
當出現多個執行緒同時競爭鎖時,會進入到 synchronizer.cpp#slow_enter
方法
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()) { 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; } ... // 這時候需要膨脹為重量級鎖,膨脹前,設定Displaced Mark Word為一個特殊值,代表該鎖正在用一個重量級鎖的monitor lock->set_displaced_header(markOopDesc::unused_mark()); //先呼叫inflate膨脹為重量級鎖,該方法返回一個ObjectMonitor物件,然後呼叫其enter方法 ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD); }
在 inflate
中完成膨脹過程。
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) { ... for (;;) { const markOop mark = object->mark() ; assert (!mark->has_bias_pattern(), "invariant") ; // mark是以下狀態中的一種: // *Inflated(重量級鎖狀態)- 直接返回 // *Stack-locked(輕量級鎖狀態) - 膨脹 // *INFLATING(膨脹中)- 忙等待直到膨脹完成 // *Neutral(無鎖狀態)- 膨脹 // *BIASED(偏向鎖)- 非法狀態,在這裡不會出現 // CASE: inflated if (mark->has_monitor()) { // 已經是重量級鎖狀態了,直接返回 ObjectMonitor * inf = mark->monitor() ; ... return inf ; } // CASE: inflation in progress if (mark == markOopDesc::INFLATING()) { // 正在膨脹中,說明另一個執行緒正在進行鎖膨脹,continue重試 TEVENT (Inflate: spin while INFLATING) ; // 在該方法中會進行spin/yield/park等操作完成自旋動作 ReadStableMark(object) ; continue ; } if (mark->has_locker()) { // 當前輕量級鎖狀態,先分配一個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 // 將鎖物件的mark word設定為INFLATING (0)狀態 markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ; if (cmp != mark) { omRelease (Self, m, true) ; continue ;// Interference -- just retry } // 棧中的displaced mark word markOop dmw = mark->displaced_mark_helper() ; assert (dmw->is_neutral(), "invariant") ; // 設定monitor的欄位 m->set_header(dmw) ; // owner為Lock Record m->set_owner(mark->locker()); m->set_object(object); ... // 將鎖物件頭設定為重量級鎖狀態 object->release_set_mark(markOopDesc::encode(m)); ... return m ; } // CASE: neutral // 分配以及初始化ObjectMonitor物件 ObjectMonitor * m = omAlloc (Self) ; // prepare m for installation - set monitor to initial state m->Recycle(); m->set_header(mark); // owner為NULL 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為重量級鎖狀態 if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) { // 不成功說明有另外一個執行緒在執行inflate,釋放monitor物件 m->set_object (NULL) ; m->set_owner(NULL) ; m->OwnerIsThread = 0 ; m->Recycle() ; omRelease (Self, m, true) ; m = NULL ; continue ; // interference - the markword changed - just retry. // The state-transitions are one-way, so there's no chance of // live-lock -- "Inflated" is an absorbing state. } ... return m ; } }
inflate
中是一個for迴圈,主要是為了處理多執行緒同時呼叫inflate的情況。然後會根據鎖物件的狀態進行不同的處理:
1.已經是重量級狀態,說明膨脹已經完成,直接返回
2.如果是輕量級鎖則需要進行膨脹操作
3.如果是膨脹中狀態,則進行忙等待
4.如果是無鎖狀態則需要進行膨脹操作
其中輕量級鎖和無鎖狀態需要進行膨脹操作,輕量級鎖膨脹流程如下:
1.呼叫 omAlloc
分配一個 ObjectMonitor
物件(以下簡稱monitor),在 omAlloc
方法中會先從執行緒私有的 monitor
集合 omFreeList
中分配物件,如果 omFreeList
中已經沒有 monitor
物件,則從JVM全域性的 gFreeList
中分配一批 monitor
到 omFreeList
中。
2.初始化 monitor
物件
3.將狀態設定為膨脹中(INFLATING)狀態
4.設定 monitor
的header欄位為 displaced mark word
,owner欄位為 Lock Record
,obj欄位為鎖物件
5.設定鎖物件頭的 mark word
為重量級鎖狀態,指向第一步分配的 monitor
物件
無鎖狀態下的膨脹流程如下:
1.呼叫 omAlloc
分配一個 ObjectMonitor
物件(以下簡稱monitor)
2.初始化 monitor
物件
3.設定 monitor
的header欄位為 mark word
,owner欄位為 null
,obj欄位為鎖物件
4.設定鎖物件頭的 mark word
為重量級鎖狀態,指向第一步分配的 monitor
物件
至於為什麼輕量級鎖需要一個膨脹中(INFLATING)狀態,程式碼中的註釋是:
// Why do we CAS a 0 into the mark-word instead of just CASing the // mark-word from the stack-locked value directly to the new inflated state? // Consider what happens when a thread unlocks a stack-locked object. // It attempts to use CAS to swing the displaced header value from the // on-stack basiclock back into the object header.Recall also that the // header value (hashcode, etc) can reside in (a) the object header, or // (b) a displaced header associated with the stack-lock, or (c) a displaced // header in an objectMonitor.The inflate() routine must copy the header // value from the basiclock on the owner's stack to the objectMonitor, all // the while preserving the hashCode stability invariants.If the owner // decides to release the lock while the value is 0, the unlock will fail // and control will eventually pass from slow_exit() to inflate.The owner // will then spin, waiting for the 0 value to disappear.Put another way, // the 0 causes the owner to stall if the owner happens to try to // drop the lock (restoring the header from the basiclock to the object) // while inflation is in-progress.This protocol avoids races that might // would otherwise permit hashCode values to change or "flicker" for an object. // Critically, while object->mark is 0 mark->displaced_mark_helper() is stable. // 0 serves as a "BUSY" inflate-in-progress indicator.
我沒太看懂,有知道的同學可以指點下~
膨脹完成之後,會呼叫 enter
方法獲得鎖
void ATTR ObjectMonitor::enter(TRAPS) { Thread * const Self = THREAD ; void * cur ; // owner為null代表無鎖狀態,如果能CAS設定成功,則當前執行緒直接獲得鎖 cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ; if (cur == NULL) { ... return ; } // 如果是重入的情況 if (cur == Self) { // TODO-FIXME: check for integer overflow!BUGID 6557169. _recursions ++ ; return ; } // 當前執行緒是之前持有輕量級鎖的執行緒。由輕量級鎖膨脹且第一次呼叫enter方法,那cur是指向Lock Record的指標 if (Self->is_lock_owned ((address)cur)) { assert (_recursions == 0, "internal state error"); // 重入計數重置為1 _recursions = 1 ; // 設定owner欄位為當前執行緒(之前owner是指向Lock Record的指標) _owner = Self ; OwnerIsThread = 1 ; return ; } ... // 在呼叫系統的同步操作之前,先嚐試自旋獲得鎖 if (Knob_SpinEarly && TrySpin (Self) > 0) { ... //自旋的過程中獲得了鎖,則直接返回 Self->_Stalled = 0 ; return ; } ... { ... for (;;) { jt->set_suspend_equivalent(); // 在該方法中呼叫系統同步操作 EnterI (THREAD) ; ... } Self->set_current_pending_monitor(NULL); } ... }
EnterI
EnterI
方法比較長,在看之前,我們先闡述下其大致原理:
一個 ObjectMonitor
物件包括這麼幾個關鍵欄位:cxq(下圖中的ContentionList),EntryList ,WaitSet,owner。
其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的連結串列結構,owner指向持有鎖的執行緒。

sync-d.png
當一個執行緒嘗試獲得鎖時,如果該鎖已經被佔用,則會將該執行緒封裝成一個 ObjectWaiter
物件插入到cxq的佇列的隊首,然後呼叫 park
函式掛起當前執行緒。在linux系統上, park
函式底層呼叫的是gclib庫的 pthread_cond_wait
,JDK的 ReentrantLock
底層也是用該方法掛起執行緒的。更多細節可以看我之前的兩篇文章: 關於同步的一點思考-下 , linux核心級同步機制--futex
當執行緒釋放鎖時,會從cxq或EntryList中挑選一個執行緒喚醒,被選中的執行緒叫做 Heir presumptive
即假定繼承人(應該是這樣翻譯),就是圖中的 Ready Thread
,假定繼承人被喚醒後會嘗試獲得鎖,但 synchronized
是非公平的,所以假定繼承人不一定能獲得鎖(這也是它叫"假定"繼承人的原因)。
如果執行緒獲得鎖後呼叫 Object#wait
方法,則會將執行緒加入到WaitSet中,當被 Object#notify
喚醒後,會將執行緒從WaitSet移動到cxq或EntryList中去。需要注意的是,當呼叫一個鎖物件的 wait
或 notify
方法時, 如當前鎖的狀態是偏向鎖或輕量級鎖則會先膨脹成重量級鎖 。
synchronized
的 monitor
鎖機制和JDK的 ReentrantLock
與 Condition
是很相似的, ReentrantLock
也有一個存放等待獲取鎖執行緒的連結串列, Condition
也有一個類似 WaitSet
的集合用來存放呼叫了 await
的執行緒。如果你之前對 ReentrantLock
有深入瞭解,那理解起 monitor
應該是很簡單。
回到程式碼上,開始分析 EnterI
方法:
void ATTR ObjectMonitor::EnterI (TRAPS) { Thread * Self = THREAD ; ... // 嘗試獲得鎖 if (TryLock (Self) > 0) { ... return ; } DeferredInitialize () ; // 自旋 if (TrySpin (Self) > 0) { ... return ; } ... // 將執行緒封裝成node節點中 ObjectWaiter node(Self) ; Self->_ParkEvent->reset() ; node._prev= (ObjectWaiter *) 0xBAD ; node.TState= ObjectWaiter::TS_CXQ ; // 將node節點插入到_cxq佇列的頭部,cxq是一個單向連結串列 ObjectWaiter * nxt ; for (;;) { node._next = nxt = _cxq ; if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ; // CAS失敗的話 再嘗試獲得鎖,這樣可以降低插入到_cxq佇列的頻率 if (TryLock (Self) > 0) { ... return ; } } // SyncFlags預設為0,如果沒有其他等待的執行緒,則將_Responsible設定為自己 if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) { Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ; } TEVENT (Inflated enter - Contention) ; int nWakeups = 0 ; int RecheckInterval = 1 ; for (;;) { if (TryLock (Self) > 0) break ; assert (_owner != Self, "invariant") ; ... // park self if (_Responsible == Self || (SyncFlags & 1)) { // 當前執行緒是_Responsible時,呼叫的是帶時間引數的park 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 { //否則直接呼叫park掛起當前執行緒 TEVENT (Inflated enter - park UNTIMED) ; Self->_ParkEvent->park() ; } if (TryLock(Self) > 0) break ; ... if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ; ... // 在釋放鎖時,_succ會被設定為EntryList或_cxq中的一個執行緒 if (_succ == Self) _succ = NULL ; // Invariant: after clearing _succ a thread *must* retry _owner before parking. OrderAccess::fence() ; } // 走到這裡說明已經獲得鎖了 assert (_owner == Self, "invariant") ; assert (object() != NULL, "invariant") ; // 將當前執行緒的node從cxq或EntryList中移除 UnlinkAfterAcquire (Self, &node) ; if (_succ == Self) _succ = NULL ; if (_Responsible == Self) { _Responsible = NULL ; OrderAccess::fence(); } ... return ; }
主要步驟有3步:
- 將當前執行緒插入到cxq佇列的隊首
- 然後park當前執行緒
- 當被喚醒後再嘗試獲得鎖
這裡需要特別說明的是 _Responsible
和 _succ
兩個欄位的作用:
當競爭發生時,選取一個執行緒作為 _Responsible
, _Responsible
執行緒呼叫的是有時間限制的 park
方法,其目的是防止出現 擱淺
現象。
_succ
執行緒是線上程釋放鎖是被設定,其含義是 Heir presumptive
,也就是我們上面說的假定繼承人。
重量級鎖的釋放
重量級鎖釋放的程式碼在 ObjectMonitor::exit
:
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) { Thread * Self = THREAD ; // 如果_owner不是當前執行緒 if (THREAD != _owner) { // 當前執行緒是之前持有輕量級鎖的執行緒。由輕量級鎖膨脹後還沒呼叫過enter方法,_owner會是指向Lock Record的指標。 if (THREAD->is_lock_owned((address) _owner)) { assert (_recursions == 0, "invariant") ; _owner = THREAD ; _recursions = 0 ; OwnerIsThread = 1 ; } else { // 異常情況:當前不是持有鎖的執行緒 TEVENT (Exit - Throw IMSX) ; assert(false, "Non-balanced monitor enter/exit!"); if (false) { THROW(vmSymbols::java_lang_IllegalMonitorStateException()); } return; } } // 重入計數器還不為0,則計數器-1後返回 if (_recursions != 0) { _recursions--;// this is simple recursive enter TEVENT (Inflated exit - recursive) ; return ; } // _Responsible設定為null if ((SyncFlags & 4) == 0) { _Responsible = NULL ; } ... for (;;) { assert (THREAD == _owner, "invariant") ; // Knob_ExitPolicy預設為0 if (Knob_ExitPolicy == 0) { // code 1:先釋放鎖,這時如果有其他執行緒進入同步塊則能獲得鎖 OrderAccess::release_store_ptr (&_owner, NULL) ;// drop the lock OrderAccess::storeload() ;// See if we need to wake a successor // code 2:如果沒有等待的執行緒或已經有假定繼承人 if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) { TEVENT (Inflated exit - simple egress) ; return ; } TEVENT (Inflated exit - complex egress) ; // code 3:要執行之後的操作需要重新獲得鎖,即設定_owner為當前執行緒 if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) { return ; } TEVENT (Exit - Reacquired) ; } ... ObjectWaiter * w = NULL ; // code 4:根據QMode的不同會有不同的喚醒策略,預設為0 int QMode = Knob_QMode ; if (QMode == 2 && _cxq != NULL) { // QMode == 2 : cxq中的執行緒有更高優先順序,直接喚醒cxq的隊首執行緒 w = _cxq ; assert (w != NULL, "invariant") ; assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ; ExitEpilog (Self, w) ; return ; } if (QMode == 3 && _cxq != NULL) { // 將cxq中的元素插入到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 ; } // Append the RATs to the EntryList // TODO: organize EntryList as a CDLL so we can locate the tail in constant-time. ObjectWaiter * Tail ; for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ; if (Tail == NULL) { _EntryList = w ; } else { Tail->_next = w ; w->_prev = Tail ; } // Fall thru into code that tries to wake a successor from EntryList } if (QMode == 4 && _cxq != NULL) { // 將cxq插入到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 ; } // Prepend the RATs to the EntryList if (_EntryList != NULL) { q->_next = _EntryList ; _EntryList->_prev = q ; } _EntryList = w ; // Fall thru into code that tries to wake a successor from EntryList } w = _EntryList; if (w != NULL) { // 如果EntryList不為空,則直接喚醒EntryList的隊首元素 assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ; ExitEpilog (Self, w) ; return ; } // EntryList為null,則處理cxq中的元素 w = _cxq ; if (w == NULL) continue ; // 因為之後要將cxq的元素移動到EntryList,所以這裡將cxq欄位設定為null for (;;) { assert (w != NULL, "Invariant") ; ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ; if (u == w) break ; w = u ; } TEVENT (Inflated exit - drain cxq into EntryList) ; assert (w != NULL, "invariant") ; assert (_EntryList== NULL, "invariant") ; if (QMode == 1) { // QMode == 1 : 將cxq中的元素轉移到EntryList,並反轉順序 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‘ // 將cxq中的元素轉移到EntryList _EntryList = w ; 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 ; } } // _succ不為null,說明已經有個繼承人了,所以不需要當前執行緒去喚醒,減少上下文切換的比率 if (_succ != NULL) continue; w = _EntryList; // 喚醒EntryList第一個元素 if (w != NULL) { guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ; ExitEpilog (Self, w) ; return ; } } }
在進行必要的鎖重入判斷以及自旋優化後,進入到主要邏輯:
code 1
設定owner為null,即釋放鎖,這個時刻其他的執行緒能獲取到鎖。這裡是一個非公平鎖的優化;
code 2
如果當前沒有等待的執行緒則直接返回就好了,因為不需要喚醒其他執行緒。或者如果說succ不為null,代表當前已經有個"醒著的"繼承人執行緒,那當前執行緒不需要喚醒任何執行緒;
code 3
當前執行緒重新獲得鎖,因為之後要操作cxq和EntryList佇列以及喚醒執行緒;
code 4
根據QMode的不同,會執行不同的喚醒策略;
根據QMode的不同,有不同的處理方式:
- QMode = 2且cxq非空:取cxq佇列隊首的ObjectWaiter物件,呼叫ExitEpilog方法,該方法會喚醒ObjectWaiter物件的執行緒,然後立即返回,後面的程式碼不會執行了;
- QMode = 3且cxq非空:把cxq佇列插入到EntryList的尾部;
- QMode = 4且cxq非空:把cxq佇列插入到EntryList的頭部;
- QMode = 0:暫時什麼都不做,繼續往下看;
只有QMode=2的時候會提前返回,等於0、3、4的時候都會繼續往下執行:
1.如果EntryList的首元素非空,就取出來呼叫ExitEpilog方法,該方法會喚醒ObjectWaiter物件的執行緒,然後立即返回;
2.如果EntryList的首元素為空,就將cxq的所有元素放入到EntryList中,然後再從EntryList中取出來隊首元素執行ExitEpilog方法,然後立即返回;
以上對QMode的歸納參考了這篇 文章 。另外說下,關於如何編譯JVM,可以看看該博主的這篇 文章 ,該博主弄了一個docker映象,傻瓜編譯~
QMode預設為0,結合上面的流程我們可以看這麼個demo:
public class SyncDemo { public static void main(String[] args) { SyncDemo syncDemo1 = new SyncDemo(); syncDemo1.startThreadA(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } syncDemo1.startThreadB(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } syncDemo1.startThreadC(); } final Object lock = new Object(); public void startThreadA() { new Thread(() -> { synchronized (lock) { System.out.println("A get lock"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("A release lock"); } }, "thread-A").start(); } public void startThreadB() { new Thread(() -> { synchronized (lock) { System.out.println("B get lock"); } }, "thread-B").start(); } public void startThreadC() { new Thread(() -> { synchronized (lock) { System.out.println("C get lock"); } }, "thread-C").start(); } }
預設策略下,在A釋放鎖後一定是C執行緒先獲得鎖。因為在獲取鎖時,是將當前執行緒插入到cxq的 頭部 ,而釋放鎖時,預設策略是:如果EntryList為空,則將cxq中的元素按原有順序插入到到EntryList,並喚醒第一個執行緒。也就是 當EntryList為空時,是後來的執行緒先獲取鎖 。這點JDK中的Lock機制是不一樣的。
Synchronized和ReentrantLock的區別
原理弄清楚了,順便總結了幾點Synchronized和ReentrantLock的區別:
- Synchronized是JVM層次的鎖實現,ReentrantLock是JDK層次的鎖實現;
- Synchronized的鎖狀態是無法在程式碼中直接判斷的,但是ReentrantLock可以通過
ReentrantLock#isLocked
判斷; - Synchronized是非公平鎖,ReentrantLock是可以是公平也可以是非公平的;
- Synchronized是不可以被中斷的,而
ReentrantLock#lockInterruptibly
方法是可以被中斷的; - 在發生異常時Synchronized會自動釋放鎖(由javac編譯時自動實現),而ReentrantLock需要開發者在finally塊中顯示釋放鎖;
- ReentrantLock獲取鎖的形式有多種:如立即返回是否成功的tryLock(),以及等待指定時長的獲取,更加靈活;
- Synchronized在特定的情況下 對於已經在等待的執行緒 是後來的執行緒先獲得鎖(上文有說),而ReentrantLock對於 已經在等待的執行緒 一定是先來的執行緒先獲得鎖;
End
總的來說Synchronized的重量級鎖和ReentrantLock的實現上還是有很多相似的,包括其資料結構、掛起執行緒方式等等。在日常使用中,如無特殊要求用Synchronized就夠了。你深入瞭解這兩者其中一個的實現,瞭解另外一個或其他鎖機制都比較容易,這也是我們常說的技術上的相通性。