JAVA併發編程--2.synchronied實現原理

synchronied實現原理

虛擬機鎖原理

虛擬機中對象頭部信息多線程

/*hotspot/src/share/vm/oops/oop.hpp*/
class oopDesc {
  friend class VMStructs;
 private:
  volatile markOop  _mark;
  union _metadata {
    Klass*      _klass;
    narrowKlass _compressed_klass;
  } _metadata;

能夠看見對象頭中結構oop

  • Mark Word:instanceOopDesc中的_mark成員,容許壓縮。它用於存儲對象的運行時記錄信息,如哈希值、GC分代年齡(Age)、鎖狀態標誌(偏向鎖、輕量級鎖、重量級鎖)、線程持有的鎖、偏向線程ID、偏向時間戳等
  • 元數據指針:instanceOopDesc中的_metadata成員,它是聯合體,能夠表示未壓縮的Klass指針(_klass)和壓縮的Klass指針。對應的klass指針指向一個存儲類的元數據的Klass對象

32位的對象頭結構,64位結構略性能

//  32 bits:
//  --------
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//             size:32 ------------------------------------------>| (CMS free block)
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)

biased_lock 表示是否偏向鎖spa

lock類型線程

  • 00 locked 輕量級鎖
  • 01 unlocked 無鎖
  • 10 monitor 排他鎖
  • 11 marked 標記

加鎖過程

代碼片斷指針

// 默認嘗試偏向鎖
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
 //是否使用偏向鎖
 if (UseBiasedLocking) {
    //未到達safepoint,嘗試重偏向
    if (!SafepointSynchronize::is_at_safepoint()) {
      BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
      if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
        return;
      }
    } else { //在safepoint進行撤銷偏向鎖
      assert(!attempt_rebias, "can not rebias toward VM thread");
      BiasedLocking::revoke_at_safepoint(obj);
    }
    //若是走到這裏則說明偏向鎖已撤銷,進行slow_enter(加輕量級鎖)
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
 }

 slow_enter (obj, lock, THREAD) ;
}

// 輕量級鎖
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對象上設置displaced mark word
    lock->set_displaced_header(mark);
    //使用CAS操做交換lock和object的mark word
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
      TEVENT (slow_enter: release stacklock) ;
      return ;
    }
    // 若是CAS失敗,則跳到下面inflate(重量級鎖)
  } else
  //若是給相同對象加鎖,則後續的鎖的displaced mark設置爲NULL(不會重複上鎖)
  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;
  }
//標記lock對象爲unused,後續由CMS回收,並調用inflate(重量級鎖)
lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}

偏向鎖

在大多數狀況下,線程之間不存在競爭關係,即一個鎖會被某個線程屢次使用。若是每次都須要申請鎖,開銷會比較大。所以出現了偏向鎖,獲取偏向鎖以後,若是是不存在其餘線程競爭鎖,那麼就不須要調用CAS來獲取鎖,以達到減小I/O的目的code

clipboard.png

加鎖過程

case 1:當該對象第一次被線程得到鎖的時候,發現是匿名偏向狀態,則會用CAS指令,將mark word中的thread id由0改爲當前線程Id。若是成功,則表明得到了偏向鎖,繼續執行同步塊中的代碼。不然,將偏向鎖撤銷,升級爲輕量級鎖。orm

case 2:當被偏向的線程再次進入同步塊時,發現鎖對象偏向的就是當前線程,在經過一些額外的檢查後(細節見後面的文章),會往當前線程的棧中添加一條Displaced Mark Word爲空的Lock Record中,而後繼續執行同步塊的代碼,由於操縱的是線程私有的棧,所以不須要用到CAS指令;因而可知偏向鎖模式下,當被偏向的線程再次嘗試得到鎖時,僅僅進行幾個簡單的操做就能夠了,在這種狀況下,synchronized關鍵字帶來的性能開銷基本能夠忽略。對象

case 3.當其餘線程進入同步塊時,發現已經有偏向的線程了,則會進入到撤銷偏向鎖的邏輯裏,通常來講,會在safepoint中去查看偏向的線程是否還存活,若是存活且還在同步塊中則將鎖升級爲輕量級鎖,原偏向的線程繼續擁有鎖,當前線程則走入到鎖升級的邏輯裏;若是偏向的線程已經不存活或者不在同步塊中,則將對象頭的mark word改成無鎖狀態(unlocked),以後再升級爲輕量級鎖。隊列

因而可知,偏向鎖升級的時機爲:當鎖已經發生偏向後,只要有另外一個線程嘗試得到偏向鎖,則該偏向鎖就會升級成輕量級鎖。固然這個說法不絕對,由於還有批量重偏向這一機制。

解鎖過程

當有其餘線程嘗試得到鎖時,是根據遍歷偏向線程的lock record來肯定該線程是否還在執行同步塊中的代碼。所以偏向鎖的解鎖很簡單,僅僅將棧中的最近一條lock record的obj字段設置爲null。須要注意的是,偏向鎖的解鎖步驟中並不會修改對象頭中的thread id。

批量重偏向與撤銷

從上文偏向鎖的加鎖解鎖過程當中能夠看出,當只有一個線程反覆進入同步塊時,偏向鎖帶來的性能開銷基本能夠忽略,可是當有其餘線程嘗試得到鎖時,就須要等到safe point時將偏向鎖撤銷爲無鎖狀態或升級爲輕量級/重量級鎖。safe point這個詞咱們在GC中常常會提到,其表明了一個狀態,在該狀態下全部線程都是暫停的(大概這麼個意思),詳細能夠看這篇文章。總之,偏向鎖的撤銷是有必定成本的,若是說運行時的場景自己存在多線程競爭的,那偏向鎖的存在不只不能提升性能,並且會致使性能降低。所以,JVM中增長了一種批量重偏向/撤銷的機制。

存在以下兩種狀況:(見官方論文第4小節):

1.一個線程建立了大量對象並執行了初始的同步操做,以後在另外一個線程中將這些對象做爲鎖進行以後的操做。這種case下,會致使大量的偏向鎖撤銷操做。

2.存在明顯多線程競爭的場景下使用偏向鎖是不合適的,例如生產者/消費者隊列。

批量重偏向(bulk rebias)機制是爲了解決第一種場景。批量撤銷(bulk revoke)則是爲了解決第二種場景。

其作法是:以class爲單位,爲每一個class維護一個偏向鎖撤銷計數器,每一次該class的對象發生偏向撤銷操做時,該計數器+1,當這個值達到重偏向閾值(默認20)時,JVM就認爲該class的偏向鎖有問題,所以會進行批量重偏向。每一個class對象會有一個對應的epoch字段,每一個處於偏向鎖狀態對象的mark word中也有該字段,其初始值爲建立該對象時,class中的epoch的值。每次發生批量重偏向時,就將該值+1,同時遍歷JVM中全部線程的棧,找到該class全部正處於加鎖狀態的偏向鎖,將其epoch字段改成新值。下次得到鎖時,發現當前對象的epoch值和class的epoch不相等,那就算當前已經偏向了其餘線程,也不會執行撤銷操做,而是直接經過CAS操做將其mark word的Thread Id 改爲當前線程Id。

當達到重偏向閾值後,假設該class計數器繼續增加,當其達到批量撤銷的閾值後(默認40),JVM就認爲該class的使用場景存在多線程競爭,會標記該class爲不可偏向,以後,對於該class的鎖,直接走輕量級鎖的流程

product(intx, BiasedLockingBulkRebiasThreshold, 20,       
      "Threshold of number of revocations per type to try to "      
      "rebias all objects in the heap of that type")                

product(intx, BiasedLockingBulkRevokeThreshold, 40,                 
      "Threshold of number of revocations per type to permanently " 
      "revoke biases of all objects in the heap of that type")

輕量級鎖

加鎖過程代碼

CASE(_monitorenter): {
    oop lockee = STACK_OBJECT(-1);
    // 建立一個空堆對象lockee
    CHECK_NULL(lockee);
    // 遍歷stack中的lock對象,尋找是否存在指向對象爲待加鎖對象的
    BasicObjectLock* limit = istate->monitor_base();
    BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
    BasicObjectLock* entry = NULL;
    while (most_recent != limit ) {
      if (most_recent->obj() == NULL) entry = most_recent;
      else if (most_recent->obj() == lockee) break;
      most_recent++;
    }
    if (entry != NULL) { //已存在鎖對象,構建一個無鎖狀態的Displaced Mark Word
    //設置到Lock Record中去
      entry->set_obj(lockee);
      markOop displaced = lockee->mark()->set_unlocked();
      entry->lock()->set_displaced_header(displaced);
      if (Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
        // 若是CAS替換不成功,表明鎖對象不是無鎖狀態,這時候判斷下是否是鎖重入
        if (THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
          entry->lock()->set_displaced_header(NULL);
        } else {
          // CAS操做失敗則調用monitorenter
          CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
        }
      }
      UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
    } else {
      istate->set_msg(more_monitors);
      UPDATE_PC_AND_RETURN(0); // Re-execute
    }
  }

解鎖過程代碼與加鎖基本相同,省略

相關文章
相關標籤/搜索