查看原文
其他

死磕Synchronized底层实现--轻量级锁

farmerjohngit Java知音 2020-08-20

点击上方“Java知音”,选择“置顶公众号”

技术文章第一时间送达!


作者:farmerjohngit

链接:https://github.com/farmerjohngit


死磕Synchronized底层实现--概论

死磕Synchronized底层实现--偏向锁


本文为死磕Synchronized底层实现第三篇文章,内容为轻量级锁实现。

轻量级锁并不复杂,其中很多内容在偏向锁一文中已提及过,与本文内容会有部分重叠

另外轻量级锁的背景和基本流程在概论中已有讲解。强烈建议在看过两篇文章的基础下阅读本文

本系列文章将对HotSpot的 synchronized锁实现进行全面分析,内容包括偏向锁、轻量级锁、重量级锁的加锁、解锁、锁升级流程的原理及源码分析,希望给在研究 synchronized路上的同学一些帮助。

本文分为两个部分:

1.轻量级锁获取流程

2.轻量级锁释放流程

本人看的JVM版本是jdk8u,具体版本号以及代码可以在这里。

http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/9ce27f0a4683/src

轻量级锁获取流程

下面开始轻量级锁获取流程分析,代码在:

http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/interpreter/bytecodeInterpreter.cpp#l1816

  1. CASE(_monitorenter): {

  2.  oop lockee = STACK_OBJECT(-1);

  3.  ...

  4.  if (entry != NULL) {

  5.   ...

  6.   // 上面省略的代码中如果CAS操作失败也会调用到InterpreterRuntime::monitorenter


  7.    // traditional lightweight locking

  8.    if (!success) {

  9.      // 构建一个无锁状态的Displaced Mark Word

  10.      markOop displaced = lockee->mark()->set_unlocked();

  11.      // 设置到Lock Record中去

  12.      entry->lock()->set_displaced_header(displaced);

  13.      bool call_vm = UseHeavyMonitors;

  14.      if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {

  15.        // 如果CAS替换不成功,代表锁对象不是无锁状态,这时候判断下是不是锁重入

  16.        // Is it simple recursive case?

  17.        if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {

  18.          entry->lock()->set_displaced_header(NULL);

  19.        } else {

  20.          // CAS操作失败则调用monitorenter

  21.          CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);

  22.        }

  23.      }

  24.    }

  25.    UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);

  26.  } else {

  27.    istate->set_msg(more_monitors);

  28.    UPDATE_PC_AND_RETURN(0); // Re-execute

  29.  }

  30. }

如果锁对象不是偏向模式或已经偏向其他线程,则 success为 false。这时候会构建一个无锁状态的 mark word设置到 LockRecord中去,我们称 LockRecord中存储对象 mark word的字段叫 DisplacedMarkWord

如果当前锁的状态不是无锁状态,则CAS失败。如果这是一次锁重入,那直接将 LockRecord的 DisplacedMarkWord设置为 null

我们看个demo,在该demo中重复3次获得锁,

  1. synchronized(obj){

  2.    synchronized(obj){

  3.        synchronized(obj){

  4.        }

  5.    }

  6. }

假设锁的状态是轻量级锁,下图反应了 mark word和线程栈中 LockRecord的状态,可以看到右边线程栈中包含3个指向当前锁对象的 LockRecord

其中栈中最高位的 LockRecord为第一次获取锁时分配的。其 DisplacedMarkword的值为锁对象的加锁前的 mark word,之后的锁重入会在线程栈中分配一个 DisplacedMarkword为 null的 LockRecord

为什么JVM选择在线程栈中添加 DisplacedMarkword为null的 LockRecord来表示重入计数呢?首先锁重入次数是一定要记录下来的,因为每次解锁都需要对应一次加锁,解锁次数等于加锁次数时,该锁才真正的被释放,也就是在解锁时需要用到说锁重入次数的。

一个简单的方案是将锁重入次数记录在对象头的 mark word中,但 mark word的大小是有限的,已经存放不下该信息了。另一个方案是只创建一个 LockRecord并在其中记录重入次数,Hotspot没有这样做的原因我猜是考虑到效率有影响:每次重入获得锁都需要遍历该线程的栈找到对应的 LockRecord,然后修改它的值。

所以最终Hotspot选择每次获得锁都添加一个 LockRecord来表示锁的重入。

接下来看看 InterpreterRuntime::monitorenter方法

  1. IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))

  2.  ...

  3.  Handle h_obj(thread, elem->obj());

  4.  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),

  5.         "must be NULL or an object");

  6.  if (UseBiasedLocking) {

  7.    // Retry fast entry if bias is revoked to avoid unnecessary inflation

  8.    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);

  9.  } else {

  10.    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);

  11.  }

  12.  ...

  13. IRT_END

fast_enter的流程在偏向锁一文已经分析过,如果当前是偏向模式且偏向的线程还在使用锁,那会将锁的 mark word改为轻量级锁的状态,同时会将偏向的线程栈中的 LockRecord修改为轻量级锁对应的形式。代码位置在:

http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/runtime/biasedLocking.cpp#l212

  1. // 线程还存活则遍历线程栈中所有的Lock Record

  2.  GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);

  3.  BasicLock* highest_lock = NULL;

  4.  for (int i = 0; i < cached_monitor_info->length(); i++) {

  5.    MonitorInfo* mon_info = cached_monitor_info->at(i);

  6.    // 如果能找到对应的Lock Record说明偏向的线程还在执行同步代码块中的代码

  7.    if (mon_info->owner() == obj) {

  8.      ...

  9.      // 需要升级为轻量级锁,直接修改偏向线程栈中的Lock Record。为了处理锁重入的case,在这里将Lock Record的Displaced Mark Word设置为null,第一个Lock Record会在下面的代码中再处理

  10.      markOop mark = markOopDesc::encode((BasicLock*) NULL);

  11.      highest_lock = mon_info->lock();

  12.      highest_lock->set_displaced_header(mark);

  13.    } else {

  14.      ...

  15.    }

  16.  }

  17.  if (highest_lock != NULL) {

  18.    // 修改第一个Lock Record为无锁状态,然后将obj的mark word设置为执行该Lock Record的指针

  19.    highest_lock->set_displaced_header(unbiased_prototype);

  20.    obj->release_set_mark(markOopDesc::encode(highest_lock));

  21.    ...

  22.  } else {

  23.    ...

  24.  }

我们看 slow_enter的流程。

  1. void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {

  2.  markOop mark = obj->mark();

  3.  assert(!mark->has_bias_pattern(), "should not see bias pattern here");

  4.  // 如果是无锁状态

  5.  if (mark->is_neutral()) {

  6.    //设置Displaced Mark Word并替换对象头的mark word

  7.    lock->set_displaced_header(mark);

  8.    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {

  9.      TEVENT (slow_enter: release stacklock) ;

  10.      return ;

  11.    }

  12.  } else

  13.  if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {

  14.    assert(lock != mark->locker(), "must not re-lock the same lock");

  15.    assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");

  16.    // 如果是重入,则设置Displaced Mark Word为null

  17.    lock->set_displaced_header(NULL);

  18.    return;

  19.  }


  20.  ...

  21.  // 走到这一步说明已经是存在多个线程竞争锁了 需要膨胀为重量级锁

  22.  lock->set_displaced_header(markOopDesc::unused_mark());

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

  24. }

轻量级锁释放流程

  1. CASE(_monitorexit): {

  2.  oop lockee = STACK_OBJECT(-1);

  3.  CHECK_NULL(lockee);

  4.  // derefing's lockee ought to provoke implicit null check

  5.  // find our monitor slot

  6.  BasicObjectLock* limit = istate->monitor_base();

  7.  BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();

  8.  // 从低往高遍历栈的Lock Record

  9.  while (most_recent != limit ) {

  10.    // 如果Lock Record关联的是该锁对象

  11.    if ((most_recent)->obj() == lockee) {

  12.      BasicLock* lock = most_recent->lock();

  13.      markOop header = lock->displaced_header();

  14.      // 释放Lock Record

  15.      most_recent->set_obj(NULL);

  16.      // 如果是偏向模式,仅仅释放Lock Record就好了。否则要走轻量级锁or重量级锁的释放流程

  17.      if (!lockee->mark()->has_bias_pattern()) {

  18.        bool call_vm = UseHeavyMonitors;

  19.        // header!=NULL说明不是重入,则需要将Displaced Mark Word CAS到对象头的Mark Word

  20.        if (header != NULL || call_vm) {

  21.          if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {

  22.            // CAS失败或者是重量级锁则会走到这里,先将obj还原,然后调用monitorexit方法

  23.            most_recent->set_obj(lockee);

  24.            CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);

  25.          }

  26.        }

  27.      }

  28.      //执行下一条命令

  29.      UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);

  30.    }

  31.    //处理下一条Lock Record

  32.    most_recent++;

  33.  }

  34.  // Need to throw illegal monitor state exception

  35.  CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);

  36.  ShouldNotReachHere();

  37. }

轻量级锁释放时需要将 DisplacedMarkWord替换到对象头的 mark word中。如果CAS失败或者是重量级锁则进入到 InterpreterRuntime::monitorexit方法中。

  1. //%note monitor_1

  2. IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))


  3.  Handle h_obj(thread, elem->obj());

  4.  ...

  5.  ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);

  6.  // Free entry. This must be done here, since a pending exception might be installed on

  7.  //释放Lock Record

  8.  elem->set_obj(NULL);

  9.  ...

  10. IRT_END

monitorexit调用完 slow_exit方法后,就释放 LockRecord

  1. void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {

  2.  fast_exit (object, lock, THREAD) ;

  3. }

  4. void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {

  5.  ...

  6.  markOop dhw = lock->displaced_header();

  7.  markOop mark ;

  8.  if (dhw == NULL) {

  9.     // 重入锁,什么也不做

  10.        ...

  11.     return ;

  12.  }


  13.  mark = object->mark() ;


  14.  // 如果是mark word==Displaced Mark Word即轻量级锁,CAS替换对象头的mark word

  15.  if (mark == (markOop) lock) {

  16.     assert (dhw->is_neutral(), "invariant") ;

  17.     if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {

  18.        TEVENT (fast_exit: release stacklock) ;

  19.        return;

  20.     }

  21.  }

  22.  //走到这里说明是重量级锁或者解锁时发生了竞争,膨胀后调用重量级锁的exit方法。

  23.  ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;

  24. }

该方法中先判断是不是轻量级锁,如果是轻量级锁则将替换 mark word,否则膨胀为重量级锁并调用 exit方法,相关逻辑将在重量级锁的文章中讲解。



更多Java技术文章,尽在【Java知音】网站。

网址:www.javazhiyin.com  ,搜索Java知音可达!


看完本文有收获?请转发分享给更多人

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存