synchronized_3_轻量级锁

161 阅读7分钟

轻量级锁介绍

轻量级锁介绍:

轻量级锁适用于少量竞争,持有锁时间短暂的情况下使用。轻量级锁不像偏向锁可以通过JVM参数控制其工作,它是无法关闭的,从加锁逻辑来说,这才是synchronized尝试获取锁的第一步,另外提一句轻量级锁是会主动释放锁的。

轻量级锁原理:

轻量级锁主要是通过CAS的方式竞争锁。大致流程为:线程获取锁,判断锁是否是无锁状态,如果是则拷贝锁对象的mark,存储到当前调用栈帧中,然后通过CAS将存储在在栈帧的lock对象设置进锁对象,如果设置成功则获取锁成功,如果加锁失败则膨胀为重量级锁。

注意:网上有很大一部分说轻量级锁是通过自旋,来进行获取锁的,并且这个自旋还是自适应的自旋。根据分析了后面锁膨胀后的源码,可以证明该结论是错误的,轻量级锁即时是使用自旋获取到了锁,此时它已经膨胀为了重量级锁,已经不是轻量级锁了,在整个轻量级锁获取期间,是没有自旋的逻辑的。

轻量级锁源码分析

轻量级锁获取锁源码分析

 // InterpreterRuntime::monitorenter
 // 还是从最开始monitorenter分析,进入slow_enter,slow_enter就是轻量级锁获取逻辑
 // 即使是进入了fast_enter最后如果不满足偏向锁条件也是会进入slow_enter方法的,因此直接从slow_enter分析
 if (UseBiasedLocking) {
 ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
 } else {
 // 如果没有使用偏向锁,则走slow_enter
 ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
 }
 // ======================================================================================
     // ObjectSynchronizer::slow_enter -> synchronizer.cpp[ObjectSynchronizer::fast_enter]
     markOop mark = obj->mark();
   assert(!mark->has_bias_pattern(), "should not see bias pattern here");
   // mark是否是无锁状态:这里可以对象头markOop.hpp源码找到方法 is_neutral();
   // bool is_neutral()  const { return (mask_bits(value(), biased_lock_mask_in_place) == unlocked_value); } 不管它是怎么计算的最后条件表达式 == unlocked_value很明显看出如果是无锁返回true
   if (mark->is_neutral()) {
     // 这里就是我们说的拷贝对象头的mark,具体拷贝到那儿呢,就是这个lock对象里面
     /*
         这个lock对象是InterpreterRuntime::monitorenter传进来的:ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
         elem是BasicObjectLock对象, 查看源码 basicLock.hpp它里面主要有两个属性
             BasicLock _lock;                                    // the lock, must be double word aligned;这个存储了锁对象的markOop,就是包了一层,提供了一个set方法
             oop       _obj;                                     // object holds the lock;关联的锁对象;用于保存关联的锁对象oop(就理解为那个java锁对象就可以了)
             这个对象主要是在进入monitorenter的时候,从当前线程栈帧中获取到的一个与该锁关联的对象,比如锁1,2,3,那么该线程栈帧中应该就有3个这种对象,它的obj就对应了锁1,2,3;并且能够复用 
     */
       
     // 核心代码,拷贝对象头到当前线程战争中,存储在BasicObjectLock._lock里面
     lock->set_displaced_header(mark);
     // 这里就是通过cas设置lock到锁对象头中了,这里的自旋是自适应自旋,也就是说它不是一次cas失败就立即返回了,这里网上资料比较少
     // 可以去看看原子指令:Atomic::cmpxchg,将lcok设置到obj()->mark_addr()地址中,比较值为mark,设置成功就返回mark,设置失败返回当前obj()->mark_addr()地址
     if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
       // CAS成功,直接返回,获取锁成功
       TEVENT (slow_enter: release stacklock) ;
       return ;
     }
     // Fall through to inflate() ...如果设置失败,没有return则进入了锁膨胀逻辑,重量级锁
   } else 
    // 这里的条件是,当前mark已经有lock了,已经是轻量级锁,并且还是当前线程,你说巧不巧,直接获取成功
    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;
   }
 ​
 lock->set_displaced_header(markOopDesc::unused_mark());
 // 锁膨胀,进入重量级锁逻辑
 ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);

到这里,就已经分析完轻量级锁加锁源码了,对比于偏向锁,可以说是比较简单了,一目了然。

总结一下:轻量级加锁slow_enter里面,如果是无锁状态,则拷贝mark到栈帧的lock对象里面,然后通过自适应自旋cas设置进锁对象中,从而完成加锁,如果自旋失败,则执行锁膨胀,升级为重量级锁。 image-20220521175720331.png

轻量级锁释放锁源码分析

 // InterpreterRuntime::monitorexit
 // 还是从InterpreterRuntime开始分析,有monitorenter,那肯定也能找到monitorexit,synchronized就是由这两个指令实现的嘛
 // 核心代码,进入
 ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
 // ----> synchronizer.cpp
 void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
   fast_exit (object, lock, THREAD) ;
 }
 // 具体释放锁的逻辑
 void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
    // 偏向锁判断,不管,能进入这里就不是偏向锁了
   assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");
   // 获取加轻量级锁,存储的mark
   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") ;
      // 重新将lock保存的 displaced_header 设置回对象头里面去,如果自旋设置失败,则说明持有轻量级锁发生了竞争,走锁膨胀逻辑 ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
      if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
         TEVENT (fast_exit: release stacklock) ;
         return;
      }
   }
 ​
   ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
 }

这里锁释放很简单,就是一行代码,将获取轻量级锁的mark重新设置回去就ok了。

整体流程

image-20220528110200388.png

关于轻量级锁的几个问题

轻量级锁如何判断在持有锁期间升级成重量级锁的?

根据上面分析,轻量级锁在释放锁的时候,会CAS将当前线程栈帧的lock里面的mark给替换回锁对象里面,如果替换失败则会升级成重量级锁,释放后唤醒等待线程。因此判断依据主要就是在持有轻量级锁期间,锁对象头被修改了,导致后面CAS失败。

轻量级锁执行CAS,这个CAS的值是什么?

CAS的值是锁对象的mark,持有锁时锁对象的mark包含了指向lock的指针。

加锁源码:mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)

解锁源码:(markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark

无论是加锁还是解锁,如果这个cas失败,最后都会走锁膨胀,加锁走锁膨胀了为了进阻塞队列解锁并升级重量级锁和唤醒等待线程

轻量级锁真的会自旋吗?

看完锁膨胀后结合synchronized重量级加锁源码,可以得出结论,轻量级锁并没有自旋获取锁的逻辑。唯一跟轻量级自旋相关的是,在轻量级锁被持有期间,如果其它线程来获取锁,是会先执行锁膨胀然后使用重量级锁也就是自旋来尝试获取锁。注意,是先膨胀后自旋获取锁,膨胀后该锁已经是重量级锁了,不可能再回到轻量级锁,因此轻量级锁跟自适应自旋获取锁没有任何关系。

在轻量级锁被持有期间,其它线程获取失败后,进入锁膨胀,按理说该lock已经处于膨胀状态,为何轻量级锁解锁失败后还要再膨胀一次?

这个问题查看源码要看了锁膨胀后才能回答,简单说下

这是获取轻量级锁失败后,膨胀的代码:ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);

这是释放轻量级锁失败后,膨胀的代码:ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;

发现有啥不一样不?很简单,都是调用了inflate方法,但是最后执行的方法是不同的,获取锁执行的是enter方法,释放锁执行的exit方法。其实是被这个inflate方法给误导了,并不是说锁膨胀方法,就一定要执行膨胀逻辑,该方法的目的是返回膨胀后的ObjectMonitor对象,也就是说在释放轻量级锁失败的情况下,这个时候拿到的ObjectMonitor是另外一个已经膨胀后的ObjectMonitor,因此核心逻辑其实在ObjectMonitor里面,最后看下它的调用方法,释放调用的exit方法,就合理了。