JVM 轻量级锁实现原理

举报
JavaEdge 发表于 2022/03/03 23:37:59 2022/03/03
【摘要】 1 轻量级锁的意义偏向锁适用于没有多线程竞争的情况,轻量级锁和重量级锁均用于多线程场景:重量级锁依赖操作系统语义,在无法获取锁时,线程直接进入阻塞状态轻量级锁会自旋一段时间,尝试获取锁,超时后再转为重量级锁。在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗。但若多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级为重量级锁,所以轻量级锁的引入并非是为完全替代重量级锁。 2 ...

1 轻量级锁的意义

偏向锁适用于没有多线程竞争的情况,轻量级锁和重量级锁均用于多线程场景:

  • 重量级锁依赖操作系统语义,在无法获取锁时,线程直接进入阻塞状态
  • 轻量级锁会自旋一段时间,尝试获取锁,超时后再转为重量级锁。

在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗。

但若多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级为重量级锁,所以轻量级锁的引入并非是为完全替代重量级锁。

2 轻量级锁的获取

当:

  • 关闭【偏向锁】功能
  • 或多个线程竞争偏向锁,导致偏向锁升级为轻量级锁

会尝试获取轻量级锁。

如果使用偏向锁,则进入 fast_enter 代码逻辑,否则进入 slow_enter 轻量级锁加锁逻辑

加锁的过程可分为两种情况来讨论:

2.1 无锁状态

无锁状态下,锁标志位为 01,偏向标志位为 0,可直接尝试加锁

Mark Word 初始状态

代码进入同步块时,同步对象处于无锁状态,锁标志位为 “01”,偏向标志位为 “0”

建立锁记录

第一步:加锁前,虚拟机需要在当前线程的栈帧中建立锁记录(Lock Record)的空间。

复制锁对象的 Mark Word

第二步:将锁对象的 Mark Word 复制到Lock Record中,这复制过来的记录叫 displaced Mark Word,就是将 mark word 放到锁记录的 _displaced_header 属性。

CAS 更新锁对象的 Mark Word

第三步:虚拟机使用 CAS 尝试将锁对象的 Mark Word 更新为指向锁记录的指针。

若更新成功,该线程就获得了该对象的锁。

...
mark = object->mark() ;

// If the object is stack-locked by the current thread, try to
// swing the displaced header from the box back to the mark.
if (mark == (markOop) lock) {
  assert (dhw->is_neutral(), "invariant") ;
  // 将 Displaced Mark Word 通过 CAS 替换回去
  if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
    TEVENT (fast_exit: release stacklock) ;
    return;
  }
}

2.2 有锁状态

有锁状态下:

  • 若是当前线程持有的轻量级锁,则说明是重入,无需争抢锁

  • 否则,说明有多个线程竞争,轻量级锁需【升级】为重量级锁

当前线程持有锁

对应无锁状态的第二步:锁对象处于加锁状态,并且锁对象的 Mark Word 指向当前线程的栈帧范围内,说明当前线程已经持有该轻量级锁,再次获取到该锁,也就是锁重入。

此时不需要争抢锁,可执行同步代码

每次获取轻量级锁时都会创建一个 Lock Record,锁重入时会创建多个指向同一个 Object 的 Lock Record,除第一次设置 Displaced Mark Word ,后面均置 null。

不是当前线程持有的锁

存在多个线程竞争锁,轻量级锁要inflate:膨胀成重量级锁后再加锁

在没有开启偏向锁的时候,加锁时会直接进入 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");

  if (mark->is_neutral()) {
    // Anticipate successful CAS -- the ST of the displaced mark must
    // be visible <= the ST performed by the CAS.
    // 将Mark Word保存在Lock Record
    lock->set_displaced_header(mark);
    // lock: 指向Lock Record的指针
		// obj()->mark_addr(): 锁对象的Mark Word地址
		// mark: 锁对象的Mark Word
    if (mark == obj()->cas_set_mark((markOop) lock, mark)) {
      return;
    }
    // Fall through to inflate() ...
    // Mark Word 处于加锁状态,当前线程持有的锁(Mark Word 指向的是当前线程的栈帧地址范围)
  } 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");
    // 锁重入,将 Displaced Mark Word 置 null
    lock->set_displaced_header(NULL);
    return;
  }

  // The object header will never be displaced to this lock,
  // so it does not matter what the value is, except that it
  // must be non-zero to avoid looking like a re-entrant lock,
  // and must not look locked either.
  lock->set_displaced_header(markOopDesc::unused_mark());
  inflate(THREAD, obj(), inflate_cause_monitor_enter)->enter(THREAD);
}

1、markOop mark = obj->mark()方法获取对象的markOop数据mark;
2、mark->is_neutral()方法判断mark是否为无锁状态:mark的偏向锁标志位为 0,锁标志位为 01
3、如果mark处于无锁状态,则进入步骤(4),否则执行步骤(6);
4、把mark保存到BasicLock对象的_displaced_header字段;
5、通过CAS尝试将Mark Word更新为指向BasicLock对象的指针,如果更新成功,表示竞争到锁,则执行同步代码,否则执行步骤(6);
6、如果当前mark处于加锁状态,且mark中的ptr指针指向当前线程的栈帧,则执行同步代码,否则说明有多个线程竞争轻量级锁,轻量级锁需要膨胀升级为重量级锁;

假设线程A和B同时执行到临界区if (mark->is_neutral())
1、线程AB都把Mark Word复制到各自的_displaced_header字段,该数据保存在线程的栈帧上,是线程私有的;
2、Atomic::cmpxchg_ptr原子操作保证只有一个线程可以把指向栈帧的指针复制到Mark Word,假设此时线程A执行成功,并返回继续执行同步代码块;
3、线程B执行失败,退出临界区,通过ObjectSynchronizer::inflate方法开始膨胀锁;

获取轻量级锁失败时会先

1.锁膨胀

锁膨胀时,若锁状态为 INFLATING 或尝试修改锁状态为 INFLATING 失败,则会进行循环尝试

2.再进入重量级锁加锁过程

ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);

3 轻量级锁的释放

解锁思路就是通过 CAS 将当前线程的栈帧中的 Displaced Mark Word 替换回锁对象中去。若替换成功,则解锁成功。

CAS 替换回 Mark Word

替换失败

替换失败,轻量级锁膨胀成重量级锁后再解锁。

这CAS替换操作可拆解为:

  1. 获取锁对象的 mark word
  2. 判断 mark word 正是指向 LockRecord 的指针
  3. CAS 原子替换:判断对象的 mark word 对应地址的值,看是否为第一步取到的 mark word,如果是则替换成功

替换失败则说明:在第 1 步和第 3 步之间,锁对象 mark word 对应地址的值已经被改掉了

比如在这之间有另外一个线程加锁,因为当前线程还未释放掉锁,所以触发了锁膨胀,修改了锁对象的 mark word 值

ObjectSynchronizer::fast_exit

1、确保处于偏向锁状态时不会执行这段逻辑;
2、取出在获取轻量级锁时保存在BasicLock对象的mark数据dhw;
3、通过CAS尝试把dhw替换到当前的Mark Word,如果CAS成功,说明成功的释放了锁,否则执行步骤(4);
4、如果CAS失败,说明有其它线程在尝试获取该锁,这时需要将该锁升级为重量级锁,并释放

锁重入

加锁时,如果是锁重入,会将 Displaced Mark Word 设置为 null。相对应地,解锁时,如果判断 Displaced Mark Word 为 null 则说明是锁重入,不做替换操作

void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
  markOop mark = object->mark();
  // We cannot check for Biased Locking if we are racing an inflation.
  assert(mark == markOopDesc::INFLATING() ||
         !mark->has_bias_pattern(), "should not see bias pattern here");

  markOop dhw = lock->displaced_header();
  if (dhw == NULL) {
    // If the displaced header is NULL, then this exit matches up with
    // a recursive enter. No real work to do here except for diagnostics.
#ifndef PRODUCT
    if (mark != markOopDesc::INFLATING()) {
      // Only do diagnostics if we are not racing an inflation. Simply
      // exiting a recursive enter of a Java Monitor that is being
      // inflated is safe; see the has_monitor() comment below.
      assert(!mark->is_neutral(), "invariant");
      assert(!mark->has_locker() ||
             THREAD->is_lock_owned((address)mark->locker()), "invariant");
      if (mark->has_monitor()) {
        // The BasicLock's displaced_header is marked as a recursive
        // enter and we have an inflated Java Monitor (ObjectMonitor).
        // This is a special case where the Java Monitor was inflated
        // after this thread entered the stack-lock recursively. When a
        // Java Monitor is inflated, we cannot safely walk the Java
        // Monitor owner's stack and update the BasicLocks because a
        // Java Monitor can be asynchronously inflated by a thread that
        // does not own the Java Monitor.
        ObjectMonitor * m = mark->monitor();
        assert(((oop)(m->object()))->mark() == mark, "invariant");
        assert(m->is_entered(THREAD), "invariant");
      }
    }
#endif
    return;
  }

参考

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。