Synchronized之轻量级锁


在偏向锁出现竞争后,加锁失败的线程会把Mark Word中的锁状态改为轻量级锁,这样其他线程再来时就会走向轻量级锁的加锁流程。下面开始轻量级锁获取流程分析,代码在bytecodeInterpreter.cpp#1816

CASE(_monitorenter): {
  oop lockee = STACK_OBJECT(-1);
  ...
  if (entry != NULL) {
   ...
   // 上面省略的代码中如果CAS操作失败也会调用到InterpreterRuntime::monitorenter

    // traditional lightweight locking
    if (!success) {
      // 构建一个无锁状态的Displaced Mark Word
      markOop displaced = lockee->mark()->set_unlocked();
      // 设置到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替换不成功,代表锁对象不是无锁状态,这时候判断下是不是锁重入
        // 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操作失败则调用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
  }
}

如果锁对象不是偏向模式或已经偏向其他线程,则successfalse。这时候会构建一个无锁状态的mark word设置到Lock Record中去,我们称Lock Record中存储对象mark word的字段叫Displaced Mark Word。需要注意的是这个Displaced Mark Word不是一个指针,而是一个副本。你或许很奇怪,每次加锁的时候都会创建Lock Record,那么Lock Record在线程中到底是个什么样的数据结构呢,这里我把加锁的过程画了出来,连同重量级锁加锁过程一起画了,因为他们是一个动态转换的过程,分开的话隔远了看着累。

轻量级锁 -> 重量级锁

Synchronized锁升级过程为:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。

  1. 假设线程进入轻量级锁。当线程执行到同步方法或方法中的同步代码块时,线程首先会创建锁记录对象(Lock Record),每个线程的有synchronized关键字的栈帧(每个方法就是一个栈帧)都会包含锁记录结构,内部可以存储锁定对象的mark work

  2. 当线程出现竞争时,让锁记录中Object Reference指向锁对象,并尝试cas替换锁对象的Mark Work,将Mark Word的值存入Lock Record的地址,注意此时锁对象头中的锁状态是 00 ,表示线程获得轻量级锁,而Lock Record中的对象头副本是 01。这是因为Lock Record中存储的是修改之前的对象头副本

  3. synchronized可重入的,当下次加锁线程还是当前线程时,轻量级锁会创建一个新的Lock Record作为重入的标识,object reference依旧指向object对象,但此时的锁记录副本为 null

  4. 当退出同步代码块时,如果锁记录为null,直接出栈,表示重入计数减一

  5. 当退出同步代码块时,锁记录不为null,这时使用cas将Lock Record中的Mark Word的副本值恢复给对象头,如果修改成功,则解锁成功。如果失败,说明有其它线程要进入同步代码块,锁已经发生锁膨胀,进入重量级解锁流程

    如果有线程(Thread-A)已经占有了轻量级锁,另一个线程(Thread-B)再次进入同步代码块,检测到对象的mark work的标识为00,则会发生锁膨胀,将锁转变为重量级锁。为object申请Monitor锁,并让mark word指向Monitor,自己进入Monitor对象的entryList进行等待,此时Ower指向锁记录对象的对象引用

    当Thread-A执行完同步代码块的代码,使用cas将Mark word的值恢复给对象头,此时会失败。这时为进入重量级解锁流程,按照Monitor地址找到Monitor对象,将Owner设置为null,并唤醒EntryList中等待的线程。


Author: 顺坚
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source 顺坚 !
评论
 Previous
Umi和Dva框架指南 Umi和Dva框架指南
使用蚂蚁金服的Antd框架有一年多了,现在感觉越用越顺手。但Antd只是个UI框架,这些强大功能的背后是Umi和Dva的功劳,路由和Model经过简单的配置就可以使用,在一个组件中还可以实现多个Model的注入。这种丝滑程度,让我不禁想起了
2021-06-27
Next 
Synchronized之偏向锁 Synchronized之偏向锁
synchronized 是我们日常使用频率很高的关键字,它是控制线程安全的同步锁。虽然开发中一直使用,但对于它的实现原理一直理解不够透彻,在面试中也是经常被问,对于一些高级资深的岗位来说,不仅仅只是简单的问一下synchronized 的
2021-04-30
  TOC