死磕Synchronized底層實(shí)現(xiàn)--輕量級(jí)鎖

本文為死磕Synchronized底層實(shí)現(xiàn)第三篇文章,內(nèi)容為輕量級(jí)鎖實(shí)現(xiàn)。

輕量級(jí)鎖并不復(fù)雜迫淹,其中很多內(nèi)容在偏向鎖一文中已提及過(guò),與本文內(nèi)容會(huì)有部分重疊为严。

另外輕量級(jí)鎖的背景和基本流程在概論中已有講解敛熬。強(qiáng)烈建議在看過(guò)兩篇文章的基礎(chǔ)下閱讀本文

本系列文章將對(duì)HotSpot的synchronized鎖實(shí)現(xiàn)進(jìn)行全面分析第股,內(nèi)容包括偏向鎖应民、輕量級(jí)鎖、重量級(jí)鎖的加鎖、解鎖诲锹、鎖升級(jí)流程的原理及源碼分析繁仁,希望給在研究synchronized路上的同學(xué)一些幫助。主要包括以下幾篇文章:

死磕Synchronized底層實(shí)現(xiàn)--概論

死磕Synchronized底層實(shí)現(xiàn)--偏向鎖

死磕Synchronized底層實(shí)現(xiàn)--輕量級(jí)鎖

死磕Synchronized底層實(shí)現(xiàn)--重量級(jí)鎖

更多文章見(jiàn)個(gè)人博客:https://github.com/farmerjohngit/myblog

本文分為兩個(gè)部分:

1.輕量級(jí)鎖獲取流程

2.輕量級(jí)鎖釋放流程

本人看的JVM版本是jdk8u归园,具體版本號(hào)以及代碼可以在這里看到黄虱。

輕量級(jí)鎖獲取流程

下面開(kāi)始輕量級(jí)鎖獲取流程分析,代碼在bytecodeInterpreter.cpp#1816庸诱。

CASE(_monitorenter): {
  oop lockee = STACK_OBJECT(-1);
  ...
  if (entry != NULL) {
   ...
   // 上面省略的代碼中如果CAS操作失敗也會(huì)調(diào)用到InterpreterRuntime::monitorenter

    // traditional lightweight locking
    if (!success) {
      // 構(gòu)建一個(gè)無(wú)鎖狀態(tài)的Displaced Mark Word
      markOop displaced = lockee->mark()->set_unlocked();
      // 設(shè)置到Lock Record中去
      entry->lock()->set_displaced_header(displaced);
      bool call_vm = UseHeavyMonitors;
      if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
        // 如果CAS替換不成功悬钳,代表鎖對(duì)象不是無(wú)鎖狀態(tài),這時(shí)候判斷下是不是鎖重入
        // Is it simple recursive case?
        if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
          entry->lock()->set_displaced_header(NULL);
        } else {
          // CAS操作失敗則調(diào)用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
  }
}

如果鎖對(duì)象不是偏向模式或已經(jīng)偏向其他線程偶翅,則successfalse。這時(shí)候會(huì)構(gòu)建一個(gè)無(wú)鎖狀態(tài)的mark word設(shè)置到Lock Record中去碉渡,我們稱(chēng)Lock Record中存儲(chǔ)對(duì)象mark word的字段叫Displaced Mark Word聚谁。

如果當(dāng)前鎖的狀態(tài)不是無(wú)鎖狀態(tài),則CAS失敗滞诺。如果這是一次鎖重入形导,那直接將Lock RecordDisplaced Mark Word設(shè)置為null

我們看個(gè)demo习霹,在該demo中重復(fù)3次獲得鎖朵耕,

synchronized(obj){
    synchronized(obj){
        synchronized(obj){
        }
    }
}

假設(shè)鎖的狀態(tài)是輕量級(jí)鎖,下圖反應(yīng)了mark word和線程棧中Lock Record的狀態(tài)淋叶,可以看到右邊線程棧中包含3個(gè)指向當(dāng)前鎖對(duì)象的Lock Record阎曹。其中棧中最高位的Lock Record為第一次獲取鎖時(shí)分配的。其Displaced Mark word的值為鎖對(duì)象的加鎖前的mark word煞檩,之后的鎖重入會(huì)在線程棧中分配一個(gè)Displaced Mark wordnullLock Record处嫌。

image

為什么JVM選擇在線程棧中添加Displaced Mark word為null的Lock Record來(lái)表示重入計(jì)數(shù)呢?首先鎖重入次數(shù)是一定要記錄下來(lái)的斟湃,因?yàn)槊看谓怄i都需要對(duì)應(yīng)一次加鎖熏迹,解鎖次數(shù)等于加鎖次數(shù)時(shí),該鎖才真正的被釋放凝赛,也就是在解鎖時(shí)需要用到說(shuō)鎖重入次數(shù)的注暗。一個(gè)簡(jiǎn)單的方案是將鎖重入次數(shù)記錄在對(duì)象頭的mark word中,但mark word的大小是有限的墓猎,已經(jīng)存放不下該信息了捆昏。另一個(gè)方案是只創(chuàng)建一個(gè)Lock Record并在其中記錄重入次數(shù),Hotspot沒(méi)有這樣做的原因我猜是考慮到效率有影響:每次重入獲得鎖都需要遍歷該線程的棧找到對(duì)應(yīng)的Lock Record毙沾,然后修改它的值屡立。

所以最終Hotspot選擇每次獲得鎖都添加一個(gè)Lock Record來(lái)表示鎖的重入。

接下來(lái)看看InterpreterRuntime::monitorenter方法

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
  ...
  Handle h_obj(thread, elem->obj());
  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
         "must be NULL or an object");
  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);
  }
  ...
IRT_END

fast_enter的流程在偏向鎖一文已經(jīng)分析過(guò),如果當(dāng)前是偏向模式且偏向的線程還在使用鎖膨俐,那會(huì)將鎖的mark word改為輕量級(jí)鎖的狀態(tài)勇皇,同時(shí)會(huì)將偏向的線程棧中的Lock Record修改為輕量級(jí)鎖對(duì)應(yīng)的形式。代碼位置在biasedLocking.cpp#212焚刺。

 // 線程還存活則遍歷線程棧中所有的Lock Record
  GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);
  BasicLock* highest_lock = NULL;
  for (int i = 0; i < cached_monitor_info->length(); i++) {
    MonitorInfo* mon_info = cached_monitor_info->at(i);
    // 如果能找到對(duì)應(yīng)的Lock Record說(shuō)明偏向的線程還在執(zhí)行同步代碼塊中的代碼
    if (mon_info->owner() == obj) {
      ...
      // 需要升級(jí)為輕量級(jí)鎖敛摘,直接修改偏向線程棧中的Lock Record。為了處理鎖重入的case乳愉,在這里將Lock Record的Displaced Mark Word設(shè)置為null兄淫,第一個(gè)Lock Record會(huì)在下面的代碼中再處理
      markOop mark = markOopDesc::encode((BasicLock*) NULL);
      highest_lock = mon_info->lock();
      highest_lock->set_displaced_header(mark);
    } else {
      ...
    }
  }
  if (highest_lock != NULL) {
    // 修改第一個(gè)Lock Record為無(wú)鎖狀態(tài),然后將obj的mark word設(shè)置為執(zhí)行該Lock Record的指針
    highest_lock->set_displaced_header(unbiased_prototype);
    obj->release_set_mark(markOopDesc::encode(highest_lock));
    ...
  } else {
    ...
  }

我們看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");
  // 如果是無(wú)鎖狀態(tài)
  if (mark->is_neutral()) {
    //設(shè)置Displaced Mark Word并替換對(duì)象頭的mark word
    lock->set_displaced_header(mark);
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
      TEVENT (slow_enter: release stacklock) ;
      return ;
    }
  } 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");
    // 如果是重入捕虽,則設(shè)置Displaced Mark Word為null
    lock->set_displaced_header(NULL);
    return;
  }

  ...
  // 走到這一步說(shuō)明已經(jīng)是存在多個(gè)線程競(jìng)爭(zhēng)鎖了 需要膨脹為重量級(jí)鎖
  lock->set_displaced_header(markOopDesc::unused_mark());
  ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}

輕量級(jí)鎖釋放流程

CASE(_monitorexit): {
  oop lockee = STACK_OBJECT(-1);
  CHECK_NULL(lockee);
  // derefing's lockee ought to provoke implicit null check
  // find our monitor slot
  BasicObjectLock* limit = istate->monitor_base();
  BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
  // 從低往高遍歷棧的Lock Record
  while (most_recent != limit ) {
    // 如果Lock Record關(guān)聯(lián)的是該鎖對(duì)象
    if ((most_recent)->obj() == lockee) {
      BasicLock* lock = most_recent->lock();
      markOop header = lock->displaced_header();
      // 釋放Lock Record
      most_recent->set_obj(NULL);
      // 如果是偏向模式,僅僅釋放Lock Record就好了坡脐。否則要走輕量級(jí)鎖or重量級(jí)鎖的釋放流程
      if (!lockee->mark()->has_bias_pattern()) {
        bool call_vm = UseHeavyMonitors;
        // header!=NULL說(shuō)明不是重入泄私,則需要將Displaced Mark Word CAS到對(duì)象頭的Mark Word
        if (header != NULL || call_vm) {
          if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
            // CAS失敗或者是重量級(jí)鎖則會(huì)走到這里,先將obj還原备闲,然后調(diào)用monitorexit方法
            most_recent->set_obj(lockee);
            CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);
          }
        }
      }
      //執(zhí)行下一條命令
      UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
    }
    //處理下一條Lock Record
    most_recent++;
  }
  // Need to throw illegal monitor state exception
  CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);
  ShouldNotReachHere();
}

輕量級(jí)鎖釋放時(shí)需要將Displaced Mark Word替換到對(duì)象頭的mark word中晌端。如果CAS失敗或者是重量級(jí)鎖則進(jìn)入到InterpreterRuntime::monitorexit方法中。

//%note monitor_1
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
 
  Handle h_obj(thread, elem->obj());
  ...
  ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
  // Free entry. This must be done here, since a pending exception might be installed on
  //釋放Lock Record
  elem->set_obj(NULL);
  ...
IRT_END

monitorexit調(diào)用完slow_exit方法后,就釋放Lock Record恬砂。

void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
  fast_exit (object, lock, THREAD) ;
}
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
  ...
  markOop dhw = lock->displaced_header();
  markOop mark ;
  if (dhw == NULL) {
     // 重入鎖咧纠,什么也不做
     ...
     return ;
  }

  mark = object->mark() ;

  // 如果是mark word==Displaced Mark Word即輕量級(jí)鎖,CAS替換對(duì)象頭的mark word
  if (mark == (markOop) lock) {
     assert (dhw->is_neutral(), "invariant") ;
     if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
        TEVENT (fast_exit: release stacklock) ;
        return;
     }
  }
  //走到這里說(shuō)明是重量級(jí)鎖或者解鎖時(shí)發(fā)生了競(jìng)爭(zhēng)泻骤,膨脹后調(diào)用重量級(jí)鎖的exit方法漆羔。
  ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}

該方法中先判斷是不是輕量級(jí)鎖,如果是輕量級(jí)鎖則將替換mark word狱掂,否則膨脹為重量級(jí)鎖并調(diào)用exit方法钧椰,相關(guān)邏輯將在重量級(jí)鎖的文章中講解。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末符欠,一起剝皮案震驚了整個(gè)濱河市嫡霞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌希柿,老刑警劉巖诊沪,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異曾撤,居然都是意外死亡端姚,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)挤悉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)渐裸,“玉大人,你說(shuō)我怎么就攤上這事』杈椋” “怎么了尚氛?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)洞渤。 經(jīng)常有香客問(wèn)我阅嘶,道長(zhǎng),這世上最難降的妖魔是什么载迄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任讯柔,我火速辦了婚禮,結(jié)果婚禮上护昧,老公的妹妹穿的比我還像新娘魂迄。我一直安慰自己,他們只是感情好惋耙,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布捣炬。 她就那樣靜靜地躺著,像睡著了一般怠晴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浴捆,一...
    開(kāi)封第一講書(shū)人閱讀 52,328評(píng)論 1 310
  • 那天蒜田,我揣著相機(jī)與錄音,去河邊找鬼选泻。 笑死冲粤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的页眯。 我是一名探鬼主播梯捕,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼窝撵!你這毒婦竟也來(lái)了傀顾?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤碌奉,失蹤者是張志新(化名)和其女友劉穎短曾,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體赐劣,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嫉拐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了魁兼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婉徘。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盖呼,到底是詐尸還是另有隱情儒鹿,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布塌计,位于F島的核電站挺身,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏锌仅。R本人自食惡果不足惜章钾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望热芹。 院中可真熱鬧贱傀,春花似錦、人聲如沸伊脓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)报腔。三九已至株搔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纯蛾,已是汗流浹背纤房。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翻诉,地道東北人炮姨。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像碰煌,于是被迫代替她去往敵國(guó)和親舒岸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359