- 說到輕量級鎖,我們必須先說一下輕量級鎖是什么?
synchronized在JDK1.6之后的優化鎖后,一共有四種鎖階段:
無鎖 --> 偏向鎖 --> 輕量級鎖 --> 重量級鎖
而重量級鎖,正處於是第四種階段,即當某個線程長時間占有鎖資源,而其他線程一直處於自旋狀態並競爭激烈,鎖將會升級為重量級鎖。
- 子曾經曰過,“Don't BB,Look at the picture”
那么這個組件都是個啥呢?
ContentionList:所有請求鎖的線程將被首先放在該競爭隊列。 EntryList:ContentList中有資格成為候選人的線程被移到EntryList中。 WaitSet:存放那些調用wait()方法被阻塞的線程 OnDeck:任何時刻最多只能有一個線程競爭鎖,該線程成為OnDeck Owner:獲得鎖的線程
當一個線程嘗試獲取鎖時,如果該鎖已經被占用,那么會將該線程封裝成一個【ObjectWaiter】對象,
插入到【ContentionList】中,然后調用【park】函數,將當前線程阻塞掛起,釋放CPU資源
(在Linux上,pack函數式直接調用底層gdlib庫的【pthread_cond_wait】,ReentrantLock底層調用的【Unsafe.park()】也是調用的這個底層)
當線程釋放鎖時,會從【ContentionList】或【EntryList】中隨機喚醒一個線程,被喚醒的線程則放入【Ready Thread】中,
【onDeck】可以理解為是“假定繼承人”,由於synchronized是非公平的,所以假定繼承人也不一定能拿到鎖。
若某個線程獲取到鎖后調用【Object#wait】方法,則會將線程加入到【WaitSet中】中,線程處於阻塞狀態,
當被【Object#notify】喚醒后,會將線程從【WaitSet】中移動到【ContentionList】或【EntryList】中。
此處要注意一點:若一個鎖對象調用wait或notify,若當前鎖級別是輕量級鎖或偏向鎖,那么將膨脹為重量級鎖。
- 我們從一段源碼來分析一下整體過程:
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 ; }
- 那么重量級鎖是如何實現重入的呢?
在Monitor中其實還有一個計數器,主要是用來記錄重入次數的,當計數器為0時,表示沒有任何線程持有鎖,
當某線程獲取鎖時,計算器則加1,若當前線程再次獲取鎖時,計數器則會再次遞增,
不過sychronized屬於隱式鎖,因為不需要手動解鎖;而ReentrantLock屬於顯式鎖,需要通過unlock開解鎖。
下面代碼就是synchronized的鎖重入源碼:
// 如果是重入的情況 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 ; }
- 上文提到了很多次ReentrantLock,所以最后我再總結一下ReentrantLock和Synchronized的兩者區別:
1》synchronized是JVM層面的鎖;ReentrantLock是JDK層面的鎖,由java代碼實現 2》synchronized鎖無法在代碼中判斷是否有所;ReentrantLock則可以通過【isLock()】判斷是否獲取到鎖 3》synchronized是一種非公平鎖;ReentrantLock既可以實現公平鎖,也可以實現非公平鎖 4》synchronized不可以被中斷;ReentrantLock可以【lockInterruptibly】實現中斷 5》發生異常時,synchronized會自動釋放鎖,有javac實現;ReentrantLock需要開發者在finally中顯式釋放鎖 6》ReentrantLock在加鎖時會更靈活,可以使用【tryLock】嘗試獲取鎖,從而避免線程阻塞
- 總結
synchronized底層Monitor其實和ReentrantLock實現上還是有很多相似,比如數據結構,掛起線程方式等。
所以兩個鎖之間還是有很多通性的,通常我們用synchronized可以解決很多問題了。