当我在阅读《深入理解Java虚拟机》时,其中关于轻量级锁膨胀到重量级锁这一过程被描述在轻量级锁加锁的过程中,而在《Java并发编程的艺术》一书中膨胀这一过程则被描述为发生在轻量级锁解锁的过程中,这让我这小老弟感到十分的迷惑。膨胀这一行为到底是如何发生的,这个自旋又有什么用处呢。

Talk is cheap. Show me the code.

首先我们先打开编译器运行时的源代码(JDK8)interpreterRuntime.cpp,发现其中对Synchronization处理时有这样一段代码:

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);
  }

其中UseBiasedLocking是我们是否开启了偏向锁(-XX:+UseBiasedLocking),如果我们开启了偏向锁,那么在新分配对象时对象就会变成未锁定、未偏向但是可偏向的对象,即对象头为无Thread ID但是标志位101的对象,此时进入的是fast_enter方法,我们先不关心fast_enter与偏向锁;让我们来看看slow_enter方法:

// Interpreter/Compiler Slow Case
// This routine is used to handle interpreter/compiler slow case
// We don't need to use fast path here, because it must have been
// failed in the interpreter/compiler code.
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.
    lock->set_displaced_header(mark);
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
      TEVENT (slow_enter: release stacklock) ;
      return ;
    }
    // Fall through to inflate() ...
  } 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");
    lock->set_displaced_header(NULL);
    return;
  }

#if 0
  // The following optimization isn't particularly useful.
  if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {
    lock->set_displaced_header (NULL) ;
    return ;
  }
#endif

  // 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());
  ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}

我们会发现在加锁时,只有对象当前无锁且CAS成功或者锁已经被当前线程所持有的情况下才可以获得轻量级锁,除此之外的情况都会执行ObjectSynchronizer::inflate方法进行膨胀,可见加锁过程中的确会出现锁膨胀,inflate方法完之后我们又会进入ObjectMonitor::enter方法,而在enter方法中会发现这样一段代码:

  // Try one round of spinning *before* enqueueing Self
  // and before going through the awkward and expensive state
  // transitions.  The following spin is strictly optional ...
  // Note that if we acquire the monitor from an initial spin
  // we forgo posting JVMTI events and firing DTRACE probes.
  if (Knob_SpinEarly && TrySpin (Self) > 0) {
     assert (_owner == Self      , "invariant") ;
     assert (_recursions == 0    , "invariant") ;
     assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
     Self->_Stalled = 0 ;
     return ;
  }

这里就出现了我们平时所说的自旋,但是自旋其实发生在锁膨胀之后,真正去获取一个被阻塞后的悲观锁开销比较大,所以此处允许使用一定的自旋来进行优化(before going through the awkward and expensive state transitions)。当然JDK1.6引入了自适应自旋做了进一步的优化,而-XX:PreBlockSpin=<n>的参数在1.6就已经失效了。

既然加锁时会出现膨胀,接下来让我们看看解锁时的代码(ObjectSynchronizer::slow_exit里面只是执行了ObjectSynchronizer::fast_exit方法,所以只看fast_exit即可):

void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
  assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");
  // if displaced header is null, the previous enter is recursive enter, no-op
  markOop dhw = lock->displaced_header();
  markOop mark ;
  if (dhw == NULL) {
     // Recursive stack-lock.
     // Diagnostics -- Could be: stack-locked, inflating, inflated.
     mark = object->mark() ;
     assert (!mark->is_neutral(), "invariant") ;
     if (mark->has_locker() && mark != markOopDesc::INFLATING()) {
        assert(THREAD->is_lock_owned((address)mark->locker()), "invariant") ;
     }
     if (mark->has_monitor()) {
        ObjectMonitor * m = mark->monitor() ;
        assert(((oop)(m->object()))->mark() == mark, "invariant") ;
        assert(m->is_entered(THREAD), "invariant") ;
     }
     return ;
  }

  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") ;
     if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
        TEVENT (fast_exit: release stacklock) ;
        return;
     }
  }

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

我们会发现只有当前线程持有锁并且CAS成功才能release stacklock释放锁,而其他情况依旧会导致ObjectSynchronizer::inflate膨胀。然后会进入ObjectMonitor::exit方法进行唤醒其他线程的一些操作,但此时已经是重量级锁了。

这么看来其实两本书描述的都没有问题,轻量级锁在加锁或者解锁时只要发生竞争就会产生膨胀,但是这两本书又都描述的不是很清楚容易让人产生疑问 。果然想要了解真相还得自己吭哧吭哧去看代码。

Last modification:April 22nd, 2020 at 05:34 am
大家一起分享知识,分享快乐