【Java】嘮嘮synchronized中的重量級鎖


  • 說到輕量級鎖,我們必須先說一下輕量級鎖是什么?

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可以解決很多問題了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM