从 Monitorenter 源码看 Synchronized 锁优化的过程

2,008 阅读18分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

介绍synchronized原理的时候,很多文章中上来就讲锁优化,或者把monitorenter和锁优化一下次全部讲了,其实这样的逻辑是混乱的,因为我们在字节码层面,只能看到加synchronized和不加synchronized代码块有四个区别(详见字节码层面原理部分),无法看到锁优化的任何内容,我们其实是从字节码中定位到monitorenter/monitorexit,然后从HotSpot源码中monitorenter和monitorexit部分源码才能看到,原来synchronized是通过改变对象的对象头内容来实现加锁和释放锁的,这期间可能要经历偏向锁->轻量级锁->重量级锁的过程,而早期synchronized只有没有偏向锁和轻量级锁,只有重量级锁。从只有重量级锁到偏向锁->轻量级锁->重量级锁这个改进叫锁优化。

本文先介绍synchronized的使用方法,接着,从demo的字节码原理出发到HosSpot源码,抽丝剥茧剖析synchronized底层的锁优化原理实现。

使用

同步指的是多个线程并发访问共享数据的时候,保证共享数据在同一时刻只能被一个线程使用,而互斥是同步的一种方式,最基本的互斥手段就是synchronized

同步代码块

synchronized(this|object) 作用于对象。synchronized(类.class) 作用于class

public void func() {
    synchronized (this) {
        // ...
    }
}
​

同步静态方法

作用于当前class

public synchronized static void func() {
    // ...
}

同步实例方法

作用于当前实例

public synchronized void func () {
    // ...
}

理解与说明:

  • 先看同步的是什么。静态方法的时候锁为类,普通方法的时候锁为实例,代码块的时候锁为synchronized括号里面的对象,如果是this或者Object,那么锁就是实例,如果是类.class,锁就是类。
  • 当确定锁作用于什么之后,需要明确,当作用于类的时候,线程A和线程B分别调用类中的方法A和方法B,两个线程有一个被阻塞,另一个执行。
  • 当作用于对象的时候,如果线程A和线程B分别作用于同一个对象的方法A和方法B,那么两个线程有一个被阻塞,另一个执行。当两个线程作用于不同对象的两个方法时就不会被阻塞。
  • 当作用于对象的时候,两个线程调用的是同一个对象的同步代码块时,两个线程会同步。当作用于不同对象的同步代码块的时候,就不会同步。当作用于整个类的时候,两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。
  • 构造方法本身就是线程安全的,所以不能也不需要用synchronized修饰。

原理

JDK早期,Synchronized是重量级锁,也就是申请锁资源必须通过内核,涉及到用户态到内核态的转换,这需要经过一个中断的调用,申请完之后还需要从内核态返回到用户态。所以在JDK 1.6之后进行了优化,也就是synchronized上锁时只需要在用户态就可以实现,而不需要进行用户态和内核态之间的切换。

字节码层面原理

同步代码块

以下面代码为例

public class Counter {
    private int count = 0;
    public void increase(){
        ++count;
    }
    public int getCount(){
        return count;
    }
}

Count类的increase方法并非线程安全的,对应的字节码为

 0 aload_0
 1 dup
 2 getfield #2 <class01/Counter.count : I>
 5 iconst_1
 6 iadd
 7 putfield #2 <class01/Counter.count : I>
10 return

这里2-7行中,getfield #2指令获取count的值,iconst_1和 iadd将取出的值加1,然后putfield #2指令将之后的值写回到count字段中,这是一个典型的read-modify-write模式。如果同时有两个线程调用increase方法,各自通过getfield #2 获得了count的值,随后执行加1操作,最后各自将更新的值返回到count中,那么count只被加1,丢失一次加1操作。

此时可以通过加Synchronized关键字修饰increase方法。

public class Counter {
    private int count = 0;
    public  void increase(){
        synchronized(this) {
            ++count;
        }
    }
    public  int getCount(){
        return count;
    }
}

对应的字节码为

 0 aload_0
 1 dup
 2 astore_1
 3 monitorenter
 4 aload_0
 5 dup
 6 getfield #2 <class01/Counter.count : I>
 9 iconst_1
10 iadd
11 putfield #2 <class01/Counter.count : I>
14 aload_1
15 monitorexit
16 goto 24 (+8)
19 astore_2
20 aload_1
21 monitorexit
22 aload_2
23 athrow
24 return
  • 0-2行:将this对象引入栈中,使用dup指令复制栈顶元素,并把它保存在局部变量表1的位置,现在站上剩下this对象引用
  • 第3行:monitorenter指令尝试获取栈顶this对象的监视器锁,如果成功就继续往下执行,如果已经有其他线程持有,则进入等待状态
  • 4-11行:执行++count指令
  • 14-15行:能执行到这一步说明monitorenter已经获得this的锁,栈上没有元素了,所以调用 aload_1将局部变量表中的this对象入栈,调用monitorexit判断能否释放
  • 19-23行:执行异常处理,我们的代码没有try-catch但是字节码层面自动给我们加上了。

JVM保证一个monitor一次只能被一个线程占有。monitorenter和monitorexit是两个与监视器相关的字节码指令。当线程执行monitorenter的时候尝试获取栈顶对象的监视器monitor,也就是尝试获取锁,如果此时monitor没有被其他线程占用就获得锁,monitor计数器设置为1,当线程已经获得monitor的所有权了,monitorenter指令也会顺利执行,monitor计数器+1.如果其他线程拥有monitor的所有权,当前线程会阻塞,直到monitor计数器变为0。

当线程执行monitorexit指令的时候,监视器计数器-1,计数器为0的时候锁被释放,其他等待的线程可以尝试获得monitor的所有权.

思考:为什么我们的代码没有try-catch但是字节码层面19-23行自动给我们加上了?

因为编译器必须保证无论同步代码块中的代码以何种方式结束,代码中每次调用monitorenter必须执行对应的monitorexit指令。如果执行了monitorenter没有执行 monitorexit指令,monitor一直被占用,则其他线程没有办法获得锁。如果执行monitorexit的线程原本并没有monitor的所有权,那么执行monitorexit指令时候会抛出illegalMonitorStateException异常。为了保证这一点,编译器自动加了一个异常处理器,这个异常处理器保证了方法正常退出和异常退出均能正常释放锁。可以理解为

public void _foo() throws Throwable{
    monitorenter(lock);
    try{
        bar();
    }finally{
        monitorexit(lock);
    }
}

根据之前介绍的try-catch-finally的字节码实现原理,finally语句块会被编译器复制到方法正常退出和异常退出的地方,语义上等价于一下代码

public void _foo() throws Throwable{
    monitorenter(lock);
    try{
        bar();
        monitorexit(lock);
    }catch(Throwable e){
        monitorexit(lock);
        throw e;
    }
}

同步方法

Java代码

public class SynchronizedDemo2 {
    public synchronized void method() {
        System.out.println("synchronized 方法");
    }
}

底层源码

Synchronized同步方法的时候,在Java字节码层面,通过查看方法表的访问标志,查看是否存在ACC_SYNCHRONIZED标识,它的标志名是0x0020,如果有,就说明该方法是一个同步方法,然后隐式地调用monitorenter和monitorexit。

HotSpot层面

在JVM层面,使用Synchronized上锁就是在Markword中记录锁的信息,从而实现锁升级过程

public class Test_Sync{
  
​
    public static void main(String[] args) {
        Object o = new Object();
​
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        
        //synchronized锁定这个对象
        synchronized (o){
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }
}

输出结果分别为对象o的markword信息

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           88 f2 95 02 (10001000 11110010 10010101 00000010) (43381384)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对比后我们可以发现,对象o未被锁定的时候看到锁标志位为01(00000001),为无锁状态,而对象o被锁定的时候锁标志位为00,表示轻量级锁。

Monitorenter源码

我们进入InterpreterRuntime:: monitorenter方法

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
  if (PrintBiasedLockingStatistics) {
    Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
  }
  Handle h_obj(thread, elem->obj());
  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
         "must be NULL or an object"); 
//如果使用偏向锁,就进入Fast_enter,避免不必要的锁膨胀,如果不是偏向锁,就进入slow_enter,也就是锁升级
  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);
  }
  assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
         "must be NULL or an object");
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

上面的重点是if、else判断,UseBiasedLocking判断是否使用偏向锁,如果使用,则进入ObjectSynchronizer::fast_enter,快速进入,避免不必要的锁膨胀,如果不是偏向锁,则调用ObjectSynchronizer::slow_enter,也兜底方案

偏向锁

fast_enter,偏向锁,在safepoint的时候撤销并且定偏向锁,如果成功就返回,不成功就slow_enter,也就是锁升级。

void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
 if (UseBiasedLocking) {
     //如果使用偏向锁,那么尝试偏向
    if (!SafepointSynchronize::is_at_safepoint()) {
        //如果线程不在安全点,那么就尝试BiasedLocking::revoke_and_rebias实现撤销并且重偏向
      BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
        //偏向成功,直接返回
      if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
        return;
      }
    } else {
      assert(!attempt_rebias, "can not rebias toward VM thread");
        //进入这里说明线程在安全点并且撤销偏向
      BiasedLocking::revoke_at_safepoint(obj);
    }
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
 }
//使用兜底方案,slow_enter
 slow_enter (obj, lock, THREAD) ;
}

如果是偏向锁的话,第一个访问代码块的线程,把它的线程ID写到对象的Markword里面。

因为偏向锁是针对原始的不常发生线程争用时的方案,如果发送线程争用,那么会撤销偏向锁,然后调用slow_enter。

轻量级锁

而线程进入slow_enter,调用cmpxchg进行自旋(cmpxchg),如果成功则返回,说明获得轻量级锁;如果不成功,就进入锁膨胀

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()) {
    //将对象头部保存在lock对象中
    lock->set_displaced_header(mark);
    //通过cmpxchg进入自旋替换对象头为lock对象地址,如果替换成功则直接返回,表明获得了轻量级锁,不然继续自旋
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
      TEVENT (slow_enter: release stacklock) ;
      return ;
    }
    // 否则判断当前对象是否上锁,并且当前线程是否是锁的占有者,如果是markword的指针指向栈帧中的LR,则重入
  } 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
​
  // 代码执行到这里,说明有多个线程竞争轻量级锁,轻量级锁通过`inflate`进行膨胀升级为重量级锁
  lock->set_displaced_header(markOopDesc::unused_mark());
  ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}

这里轻量级锁是通过BasicLock对象来实现的,在线程JVM栈中产生一个LR(lock Record)的栈桢,然后他们两个CAS竞争锁,成功的,就会在Markword中记录一个指针(62位),这个指针指向竞争成功的线程的LR,另外一个线程CAS自旋继续竞争,等到前面线程用完了,才进入。这就是自旋锁的由来。

重量级锁

inflate

ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);则是进行锁膨胀,升级为重量级锁。主要分为两部,其中inflate用于获取监视器monitor,enter用于抢占锁

ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
  // Inflate mutates the heap ...
  // Relaxing assertion for bug 6320749.
  assert (Universe::verify_in_progress() ||
          !SafepointSynchronize::is_at_safepoint(), "invariant") ;
​
  for (;;) { //通过无意义的循环实现自旋操作
      const markOop mark = object->mark() ;
      assert (!mark->has_bias_pattern(), "invariant") ;
​
      if (mark->has_monitor()) {//has_monitor是markOop.hpp中的方法,如果为true表示当前锁已经是重量级锁了
          ObjectMonitor * inf = mark->monitor() ;//获得重量级锁的对象监视器直接返回
          assert (inf->header()->is_neutral(), "invariant");
          assert (inf->object() == object, "invariant") ;
          assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
          return inf ;
      }
​
      if (mark == markOopDesc::INFLATING()) {//膨胀等待,表示存在线程正在膨胀,通过continue进行下一轮的膨胀
         TEVENT (Inflate: spin while INFLATING) ;
         ReadStableMark(object) ;
         continue ;
      }
​
      if (mark->has_locker()) {//表示当前锁为轻量级锁,以下是轻量级锁的膨胀逻辑
          ObjectMonitor * m = omAlloc (Self) ;//获取一个可用的ObjectMonitor
          // Optimistically prepare the objectmonitor - anticipate successful CAS
          // We do this before the CAS in order to minimize the length of time
          // in which INFLATING appears in the mark.
          m->Recycle();
          m->_Responsible  = NULL ;
          m->OwnerIsThread = 0 ;
          m->_recursions   = 0 ;
          m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;   // Consider: maintain by type/class
          /**将object->mark_addr()和mark比较,如果这两个值相等,则将object->mark_addr()
          改成markOopDesc::INFLATING(),相等返回是mark,不相等返回的是object->mark_addr()**/
                     markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
          if (cmp != mark) {//CAS失败
             omRelease (Self, m, true) ;//释放监视器
             continue ;       // 重试
          }
​
          markOop dmw = mark->displaced_mark_helper() ;
          assert (dmw->is_neutral(), "invariant") ;
​
          //CAS成功以后,设置ObjectMonitor相关属性
          m->set_header(dmw) ;
​
​
          m->set_owner(mark->locker());
          m->set_object(object);
          // TODO-FIXME: assert BasicLock->dhw != 0.
​
​
          guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;
          object->release_set_mark(markOopDesc::encode(m));
​
​
          if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
          TEVENT(Inflate: overwrite stacklock) ;
          if (TraceMonitorInflation) {
            if (object->is_instance()) {
              ResourceMark rm;
              tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
                (void *) object, (intptr_t) object->mark(),
                object->klass()->external_name());
            }
          }
          return m ; //返回ObjectMonitor
      }
      //如果是无锁状态
      assert (mark->is_neutral(), "invariant");
      ObjectMonitor * m = omAlloc (Self) ; ////获取一个可用的ObjectMonitor
      //设置ObjectMonitor相关属性
      m->Recycle();
      m->set_header(mark);
      m->set_owner(NULL);
      m->set_object(object);
      m->OwnerIsThread = 1 ;
      m->_recursions   = 0 ;
      m->_Responsible  = NULL ;
      m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;       // consider: keep metastats by type/class
      /**将object->mark_addr()和mark比较,如果这两个值相等,则将object->mark_addr()
          改成markOopDesc::encode(m),相等返回是mark,不相等返回的是object->mark_addr()**/
      if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
          //CAS失败,说明出现了锁竞争,则释放监视器重行竞争锁
          m->set_object (NULL) ;
          m->set_owner  (NULL) ;
          m->OwnerIsThread = 0 ;
          m->Recycle() ;
          omRelease (Self, m, true) ;
          m = NULL ;
          continue ;
          // interference - the markword changed - just retry.
          // The state-transitions are one-way, so there's no chance of
          // live-lock -- "Inflated" is an absorbing state.
      }
​
      if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
      TEVENT(Inflate: overwrite neutral) ;
      if (TraceMonitorInflation) {
        if (object->is_instance()) {
          ResourceMark rm;
          tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
            (void *) object, (intptr_t) object->mark(),
            object->klass()->external_name());
        }
      }
      return m ; //返回ObjectMonitor对象
  }
}

该方法有四种情况

  1. mark->has_monitor() 说明已经膨胀为重量级锁,判断lock标志位是否为10,如果是则获得对象监视器
  2. 锁处于inflating,说明其他线程正在执行锁膨胀,那么当前线程通过自旋等待其他线程完成锁膨胀
  3. 如果当前线程已经是轻量级锁需要锁膨胀,那么通过omAlloc方法获得一个可用的ObjectMonitor,并设置初始数据;然后通过CAS将对象头设置为`markOopDesc:INFLATING,表示当前锁正在膨胀,如果CAS失败,继续自旋
  4. 如果是无锁状态,类似情况3

enter

void ATTR ObjectMonitor::enter(TRAPS) {
  // The following code is ordered to check the most common cases first
  // and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
  Thread * const Self = THREAD ;
  void * cur ;
​
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  if (cur == NULL) {//CAS成功
     // Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
     assert (_recursions == 0   , "invariant") ;
     assert (_owner      == Self, "invariant") ;
     // CONSIDER: set or assert OwnerIsThread == 1
     return ;
  }
​
  if (cur == Self) {
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions ++ ;
     return ;
  }
​
  if (Self->is_lock_owned ((address)cur)) {
    assert (_recursions == 0, "internal state error");
    _recursions = 1 ;
    // Commute owner from a thread-specific on-stack BasicLockObject address to
    // a full-fledged "Thread *".
    _owner = Self ;
    OwnerIsThread = 1 ;
    return ;
  }
​
  // We've encountered genuine contention.
  assert (Self->_Stalled == 0, "invariant") ;
  Self->_Stalled = intptr_t(this) ;
​
  // 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 ;
  }
​
  assert (_owner != Self          , "invariant") ;
  assert (_succ  != Self          , "invariant") ;
  assert (Self->is_Java_thread()  , "invariant") ;
  JavaThread * jt = (JavaThread *) Self ;
  assert (!SafepointSynchronize::is_at_safepoint(), "invariant") ;
  assert (jt->thread_state() != _thread_blocked   , "invariant") ;
  assert (this->object() != NULL  , "invariant") ;
  assert (_count >= 0, "invariant") ;
​
  // Prevent deflation at STW-time.  See deflate_idle_monitors() and is_busy().
  // Ensure the object-monitor relationship remains stable while there's contention.
  Atomic::inc_ptr(&_count);
​
  EventJavaMonitorEnter event;
​
  { // Change java thread status to indicate blocked on monitor enter.
    JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);
​
    DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
    if (JvmtiExport::should_post_monitor_contended_enter()) {
      JvmtiExport::post_monitor_contended_enter(jt, this);
    }
​
    OSThreadContendState osts(Self->osthread());
    ThreadBlockInVM tbivm(jt);
​
    Self->set_current_pending_monitor(this);
​
    // TODO-FIXME: change the following for(;;) loop to straight-line code.
    for (;;) {
      jt->set_suspend_equivalent();
      // cleared by handle_special_suspend_equivalent_condition()
      // or java_suspend_self()
​
      EnterI (THREAD) ;
​
      if (!ExitSuspendEquivalent(jt)) break ;
​
      //
      // We have acquired the contended monitor, but while we were
      // waiting another thread suspended us. We don't want to enter
      // the monitor while suspended because that would surprise the
      // thread that suspended us.
      //
          _recursions = 0 ;
      _succ = NULL ;
      exit (false, Self) ;
​
      jt->java_suspend_self();
    }
    Self->set_current_pending_monitor(NULL);
  }
...//此处省略无数行代码

主要是通过CAS判断当前线程的指针和监视器的_owner比较替换,如果成功了直接返回,如果失败了就判断当前线程是不是占用了监视器,如果是,则是重入的,次数加1,再开始竞争,竞争的方式有自旋竞争(TrySpin)和等待竞争(EnterI)

Monitorexit源码

轻量级锁释放

首先释放轻量级锁,调用monitorexit

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
  Handle h_obj(thread, elem->obj());
  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
         "must be NULL or an object");
  if (elem == NULL || h_obj()->is_unlocked()) {
    THROW(vmSymbols::java_lang_IllegalMonitorStateException());
  }
  ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
  // Free entry. This must be done here, since a pending exception might be installed on
  // exit. If it is not cleared, the exception handling code will try to unlock the monitor again.
  elem->set_obj(NULL);
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

将当前线程栈帧中锁记录空间中的Mark Word替换到锁对象的对象头中,如果成功表示锁释放成功。否则,锁膨胀成重量级锁,实现重量级锁的释放锁逻辑

重量级锁释放

void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
   Thread * Self = THREAD ;
   if (THREAD != _owner) {//如果当前锁对象中的_owner没有指向当前线程
     //如果_owner指向的BasicLock在当前线程栈上,那么将_owner指向当前线程
     if (THREAD->is_lock_owned((address) _owner)) {
       // Transmute _owner from a BasicLock pointer to a Thread address.
       // We don't need to hold _mutex for this transition.
       // Non-null to Non-null is safe as long as all readers can
       // tolerate either flavor.
       assert (_recursions == 0, "invariant") ;
       _owner = THREAD ;
       _recursions = 0 ;
       OwnerIsThread = 1 ;
     } else {
       // NOTE: we need to handle unbalanced monitor enter/exit
       // in native code by throwing an exception.
       // TODO: Throw an IllegalMonitorStateException ?
       TEVENT (Exit - Throw IMSX) ;
       assert(false, "Non-balanced monitor enter/exit!");
       if (false) {
          THROW(vmSymbols::java_lang_IllegalMonitorStateException());
       }
       return;
     }
   }
   //如果当前,线程重入锁的次数,不为0,那么就重新走ObjectMonitor::exit,直到重入锁次数为0为止
   if (_recursions != 0) {
     _recursions--;        // this is simple recursive enter
     TEVENT (Inflated exit - recursive) ;
     return ;
   }
  ...//此处省略很多代码
  for (;;) {
    if (Knob_ExitPolicy == 0) {
      OrderAccess::release_store(&_owner, (void*)NULL);   //释放锁
      OrderAccess::storeload();                        // See if we need to wake a successor
      if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
        TEVENT(Inflated exit - simple egress);
        return;
      }
      TEVENT(Inflated exit - complex egress);
      //省略部分代码...
    }
    //省略部分代码...
    ObjectWaiter * w = NULL;
    int QMode = Knob_QMode;
    //根据QMode的模式判断,
    //如果QMode == 2则直接从_cxq挂起的线程中唤醒    
    if (QMode == 2 && _cxq != NULL) {
      w = _cxq;
      ExitEpilog(Self, w);
      return;
    }
     //省略部分代码... 省略的代码为根据QMode的不同,不同的唤醒机制
  }
}

重量级锁的释放是通过 ObjectMonitor::exit来实现的,释放以后会通知被阻塞的线程去竞争锁

  1. 判断当前锁对象中的owner没有指向当前线程,如果owner指向的BasicLock在当前线程栈上,那么将_owner指向当前线程
  2. 如果当前锁对象中的_owner指向当前线程,则判断当前线程重入锁的次数,如果不为0,继续执行ObjectMonitor::exit(),直到重入锁次数为0为止
  3. 释放当前锁,并根据QMode的模式判断,是否将_cxq中挂起的线程唤醒。还是其他操作

总结

字节码层面,synchronized的原理可以理解为在字节码中加了monitorenter和monitorexit来实现的;而深入HotSpot源码的monitorenter源码部分,可以看出synchronized底层原理其实涉及到偏向锁->轻量级锁->重量级锁的锁膨胀过程。

多数时间,被synchronized修饰的代码块只有一个线程在执行,根本没有多线程竞争,这种情况没必要设置竞争机制,所以产生了偏向锁,他在monitorenter中是通过fast_enter来实现的,实际上就是第一个访问代码块的线程,把它的线程ID写到MarkWord中去。

一旦有线程来竞争了,fast_enter就撤销偏向锁,然后调用slow_enter,竞争的线程通过CAS自旋竞争,成功的线程就会在Markword中记录一个指针,指向竞争成功的线程的LR,而另一个线程CAS自旋继续竞争,等前面线程用完了才进入。

由于自旋是消耗CPU资源的,并且是无意义的,如果锁的时间过长或者自旋线程多,CPU资源会被大量消耗,而重量级锁有等待队列,拿不到锁的线程会进入等待队列,不需要消耗CPU资源(采用CFS完全公平策略,根据时间片比例分配)

所以如果线程自旋超过10次,或者自旋线程数超过CPU核数的一半,此时会升级为重量级锁,也就是调用inflate用于获取监视器monitor,enter用于抢占锁

参考

HotSpot源码

《深入理解java虚拟机》

《Java并发实现原理》