Sychronized關(guān)鍵字使用及原理解析

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)如下:

image.png

在 JDK 6 中虛擬機團隊對鎖進行了重要改進,優(yōu)化了其性能引入了 偏向鎖、輕量級鎖、適應性自旋、鎖消除、鎖粗化等實現(xiàn)。

總體上來說鎖狀態(tài)升級流程如下:


image.png

偏向鎖

流程

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要么重新偏向于其他線程,要么恢復到無鎖或者標記對象不適合作為偏向鎖,最后喚醒暫停的線程。

image.png

關(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是兩個線程同時爭奪鎖,導致鎖膨脹的流程圖。

image.png

因為自旋會消耗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)釋放了鎖,那么當前線程就可以不必阻塞而是直接獲取同步資源,從而避免切換線程的開銷。這就是自旋鎖。


image.png

自旋鎖本身是有缺點的,它不能代替阻塞。自旋等待雖然避免了線程切換的開銷,但它要占用處理器時間。如果鎖被占用的時間很短,自旋等待的效果就會非常好。反之,如果鎖被占用的時間很長,那么自旋的線程只會白浪費處理器資源。所以,自旋等待的時間必須要有一定的限度,如果自旋超過了限定次數(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é)作流程圖

image.png

源碼解析

在 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
  }
  1. 初始時為 NULL。當有線程占有該 monitor 時 owner 標記為該線程的 ID。當線程釋放 monitor 時 owner 恢復為 NULL。owner 是一個臨界資源 JVM 是通過 CAS 操作來保證其線程安全的。
  2. _cxq:競爭隊列所有請求鎖的線程首先會被放在這個隊列中(單向)。_cxq 是一個臨界資源 JVM 通過 CAS 原子指令來修改_cxq 隊列。
    每當有新來的節(jié)點入隊,它的 next 指針總是指向之前隊列的頭節(jié)點,而_cxq 指針會指向該新入隊的節(jié)點,所以是后來居上。
  3. _EntryList: _cxq 隊列中有資格成為候選資源的線程會被移動到該隊列中。
  4. _WaitSet: 等待隊列因為調(diào)用 wait 方法而被阻塞的線程會被放在該隊列中。
    monitor競爭操作
  1. 通過 CAS 嘗試把 monitor 的 owner 字段設置為當前線程。
  2. 如果設置之前的 owner 指向當前線程,說明當前線程再次進入 monitor,即 重入鎖執(zhí)行 recursions ++ , 記錄重入的次數(shù)。
  3. 如果當前線程是第一次進入該 monitor, 設置 recursions 為 1,_owner 為當前線程,該線程成功獲得鎖并返回。
  4. 如果獲取鎖失敗,則等待鎖的釋放。

執(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等待

  1. 當前線程被封裝成 ObjectWaiter 對象 node,狀態(tài)設置成 ObjectWaiter::TS_CXQ。
  2. for 循環(huán)通過 CAS 把 node 節(jié)點 push 到_cxq列表中,同一時刻可能有多個線程把自己的 node 節(jié)點 push 到_cxq列表中。
  3. node 節(jié)點 push 到_cxq 列表之后,通過自旋嘗試獲取鎖,如果還是沒有獲取到鎖則通過 park 將當前線程掛起等待被喚醒。
  4. 當該線程被喚醒時會從掛起的點繼續(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ū)別:


image.png

參考鏈接

synchronized 關(guān)鍵字詳解
并發(fā)編程的藝術(shù)
不可不說的Java“鎖”事

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容