Java中的synchronized关键字(一)

在Java中,synchronized关键字既可修饰方法,又可修饰对象,在用于对象时既可修饰普通对象也可用于类对象。

synchronized关键字

用法

  • 修饰方法:根据JVM规范2.11.10节,方法级同步是隐式的,运行时常量池method_info结构用ACC_SYNCHRONIZED标志区分synchronized方法,当调用ACC_SYNCHRONIZED修饰的方法时,执行线程会进入监视器,然后执行该方法,最后退出监视器。
  • 修饰对象:用synchronized修饰对象时通常使用synchronized块语句,JVM提供了monitorenter指令monitorexit指令支持这种特性。使用javap命令反编译字节码文件后会看到在synchronized块语句的入口和出口分别有monitorenter和monitorexit指令。

monitorenter指令

根据先前文章分析的模板解释器的初始化过程,为monitorenter指令生成汇编代码的生成函数如下:

void TemplateTable::monitorenter() {
  transition(atos, vtos);

  // 省略一些代码

  // store object
  __ movptr(Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()), rax);
  __ lock_object(c_rarg1);

  // 省略一些代码
}

lock_object函数在InterpreterMacroAssembler类中实现如下:

void InterpreterMacroAssembler::lock_object(Register lock_reg) {
  assert(lock_reg == c_rarg1, "The argument is only for looks. It must be c_rarg1");

  if (UseHeavyMonitors) {
    call_VM(noreg,
            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
            lock_reg);
  } else {
    // 省略一些代码
    if (UseBiasedLocking) {
      biased_locking_enter(lock_reg, obj_reg, swap_reg, rscratch1, false, done, &slow_case);
    }
    // 省略一些代码
    bind(slow_case);
    // Call the runtime routine for slow case
    call_VM(noreg,
            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
            lock_reg);
    bind(done);
  }
}

该函数对三种情况做了分别的处理:

  • 如果明确使用重量级的监视器(虚拟机参数-XX:+UseHeavyMonitors),那么UseHeavyMonitors的值是true,而UseBiasedLocking的值是false,会直接执行标准的加锁流程(见下文);
  • 如果使用偏向锁(默认启用),那么UseHeavyMonitors的值是false,而UseBiasedLocking的值是true。当某个线程想锁定一个可偏向的对象时首先会通过CAS操作尝试将自己的线程指针安装到对象的mark word,如果CAS操作成功则该对象被锁定并偏向了该线程,该线程在后续的加锁与解锁操作时如果对象仍然可偏向,那么只需检查mark word中的线程指针。CAS操作失败意味着该对象已经偏向了另一线程,因此该对象的偏向锁需要被撤销,偏向锁升级成轻量级锁(仍由另一线程持有);
  • 如果不使用偏向锁(虚拟机参数-XX:-UseBiasedLocking),则UseHeavyMonitors与UseBiasedLocking的值都是false,执行标准的加锁流程。

monitorexit指令

为monitorexit指令生成汇编代码的函数如下:

void TemplateTable::monitorexit() {
  // 省略一些代码
  __ unlock_object(c_rarg1);
  __ pop_ptr(rax); // discard object
}

所调用的InterpreterMacroAssembler类的unlock_object函数实现如下,与上文的lock_object函数相似。

void InterpreterMacroAssembler::unlock_object(Register lock_reg) {
  assert(lock_reg == c_rarg1, "The argument is only for looks. It must be rarg1");

  if (UseHeavyMonitors) {
    call_VM(noreg,
            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit),
            lock_reg);
  } else {
    // 省略一些代码

    // Call the runtime routine for slow case.
    movptr(Address(lock_reg, BasicObjectLock::obj_offset_in_bytes()),
         obj_reg); // restore obj
    call_VM(noreg,
            CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit),
            lock_reg);
    bind(done);
    restore_bcp();
  }
}

synchronized方法

模板解释器初始化时generate系列函数会为Java方法调用生成汇编代码,以调用普通Java方法为例,generate_normal_entry函数只有一个用来表示方法是否是同步方法的布尔型参数。如果是同步方法那么在调用真正的Java方法之前会调用lock_method函数,注意lock_method函数在内部也会调用InterpreterMacroAssembler类的lock_object函数。

void InterpreterGenerator::lock_method(void) {
  // synchronize method
  const Address access_flags(rbx, Method::access_flags_offset());
  const Address monitor_block_top(
        rbp,
        frame::interpreter_frame_monitor_block_top_offset * wordSize);
  const int entry_size = frame::interpreter_frame_monitor_size() * wordSize;

  // get synchronization object
  {
    const int mirror_offset = in_bytes(Klass::java_mirror_offset());
    Label done;
    __ movl(rax, access_flags);
    __ testl(rax, JVM_ACC_STATIC);
    // get receiver (assume this is frequent case)
    __ movptr(rax, Address(r14, Interpreter::local_offset_in_bytes(0)));
    __ jcc(Assembler::zero, done);
    __ movptr(rax, Address(rbx, Method::const_offset()));
    __ movptr(rax, Address(rax, ConstMethod::constants_offset()));
    __ movptr(rax, Address(rax,
                           ConstantPool::pool_holder_offset_in_bytes()));
    __ movptr(rax, Address(rax, mirror_offset));

    __ bind(done);
  }

  // add space for monitor & lock
  __ subptr(rsp, entry_size); // add space for a monitor entry
  __ movptr(monitor_block_top, rsp);  // set new monitor block top
  // store object
  __ movptr(Address(rsp, BasicObjectLock::obj_offset_in_bytes()), rax);
  __ movptr(c_rarg1, rsp); // object address
  __ lock_object(c_rarg1);
}

在深入InterpreterRuntime类的monitorenter和monitorexit静态函数之前,先看一下OpenJDK的Wiki对同步的解释。

同步流程

OpenJDK给出了同步加锁的流程。

Synchronization.png

  1. 右边展示了标准的加锁过程。当对象未被加锁时,mark word最低两位的值是01。当方法在对象上同步时,mark word和对象指针会保存在当前栈桢的锁记录(lock record)里。然后虚拟机尝试利用CAS操作将锁记录的指针安装到对象的mark word上,如果成功,当前线程就拥有了锁。锁记录总是在字边界对齐的,mark word的最低两位会变成00以标识该对象被锁定。
  2. 如果因为该对象之前被锁定而导致CAS操作失败,虚拟机首先会测试mark word是否指向当前线程的方法栈。如果是则这个线程已经拥有了该对象的锁(锁记录已经在当前栈桢中了),可以继续执行。对这种递归锁定的对象,锁记录会被初始化成0而不是对象的mark word。只有当两个不同线程并发地在同一对象上同步时,轻量级锁才必须膨胀成重量级的监视器(monitor)以管理等待的线程。
  3. 轻量级锁比膨胀的监视器要轻量得多,但性能也受每次CAS操作的影响,尽管大多数对象都是被同一线程锁定和解锁。在Java 6,这个不足被无存储的偏向锁技术解决,只有第一次获取锁时才执行CAS操作用以将线程ID安装到mark word上。对象偏向了这个线程,后续被同一线程加锁与解锁都不需要任何的原子操作或者去更新mark word,栈上的锁记录会一直不被初始化。
  4. 当一个线程在偏向另一线程的对象上同步时,必须撤回偏向以使该对象被正常地加锁。遍历偏向持有者的栈,与该对象有关的锁记录会被调整成轻量级锁模式,最老的锁记录的指针会被安装到对象的mark word,所有线程必须等待此操作。偏向被撤回的另一种情况是访问对象的hashcode时,因为hash与线程ID在字段上是共享的。
  5. 显式需要在线程间共享的对象,比如生产者/消费者模式中,不适用偏向锁。因此,在过去如果经常发生撤回那么某个类就会禁用偏向锁,这叫做批量撤回(bulk revocation)。如果加锁代码在被禁用偏向锁的类实例上被调用,那么会执行标准的轻量级锁操作。
  6. 相似地,批量重偏向(bulk rebiasing)使类实例的偏向无效而不禁用偏向锁,epoch值扮演着偏向有效期的时间戳。这个值会在对象分配的时候拷贝到mark word。

InterpreterRuntime类

InterpreterRuntime类的实现在文件hotspot/src/share/vm/interpreter/interpreterRuntime.cpp中,monitorenter函数代码经过预处理后如下,UseBiasedLocking是运行时参数,默认是开启的。如果使用偏向锁,则调用ObjectSynchronizer类的fast_enter静态函数,否则调用slow_enter静态函数,BasicObjectLock就是上面所说的锁记录实现。

void InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem)
{
    ThreadInVMfromJavaNoAsyncException __tiv(thread);
    HandleMarkCleaner __hm(thread);
    Thread* __the_thread__ = thread;
    os::verify_stack_alignment();
    if (PrintBiasedLockingStatistics) {
        Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
    }
    Handle h_obj(thread, elem->obj());
    if (UseBiasedLocking) {
        // Retry fast entry if bias is revoked to avoid unnecessary inflation
        ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, __the_thread__);
        if ((((ThreadShadow*)__the_thread__)->has_pending_exception())) return ; (void)(0);
    } else {
        ObjectSynchronizer::slow_enter(h_obj, elem->lock(), __the_thread__);
        if ((((ThreadShadow*)__the_thread__)->has_pending_exception())) return ; (void)(0);
    }
}

monitorexit函数代码经过预处理后如下,只调用ObjectSynchronizer类的slow_exit静态函数。

void InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem)
{
    ThreadInVMfromJavaNoAsyncException __tiv(thread);
    HandleMarkCleaner __hm(thread);
    Thread* __the_thread__ = thread;
    os::verify_stack_alignment();
    Handle h_obj(thread, elem->obj());

    if (elem == __null || h_obj()->is_unlocked()) {
        { Exceptions::_throw_msg(__the_thread__, vmSymbols::java_lang_IllegalMonitorStateException(), __null); return; };
    }
    ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
    elem->set_obj(__null);
}

ObjectSynchronizer类

fast_enter函数

在偏向锁启用的情况下,fast_enter函数首先再次尝试撤回偏向并重新偏向至参数线程,若成功则返回,否则执行标准的加锁过程。

void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
 if (UseBiasedLocking) {
    if (!SafepointSynchronize::is_at_safepoint()) {
      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 (obj, lock, THREAD) ;
}

slow_enter函数

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()) { // 检查是否为无锁状态,即obj的mark word的后两位是否为01
    // Anticipate successful CAS -- the ST of the displaced mark must
    // be visible <= the ST performed by the CAS.
    lock->set_displaced_header(mark); // 栈桢锁记录的displaced header会保存obj的mark word
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) { // 利用CAS操作将锁记录指针安装到obj的mark word上,若操作成功则拥有了锁
      TEVENT (slow_enter: release stacklock) ;
      return ;
    }
    // Fall through to inflate() ...
  } else// 若obj已被锁定,即obj的mark word的后两位已经是00,那么看它是否由参数线程锁定,即检查mark word是否指向参数线程的方法栈
  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); // 锁记录的displaced header会被置为0而不会是对象的mark word
    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());
  ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD); // 膨胀成重量级的监视器
}

slow_exit函数

slow_exit与fast_exit函数几乎相同,都是用于monitorexit字节码指令的执行。与slow_enter函数相似,fast_exit函数同样对锁的不同情况做了分类讨论。

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");
  // if displaced header is null, the previous enter is recursive enter, no-op
  markOop dhw = lock->displaced_header();
  markOop mark ;
  if (dhw == NULL) { // 在slow_enter函数里,递归重入时锁记录的displaced header会被初始化成0
     // Recursive stack-lock.
     // Diagnostics -- Could be: stack-locked, inflating, inflated.
     mark = object->mark() ; // 对象自己的mark word
     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() ; // 对象自己的mark word

  // 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) { // 如果是从无锁转变到轻量级锁,那么锁记录的displaced header保存的是对象加锁之前的mark word
     assert (dhw->is_neutral(), "invariant") ; // 对象加锁之前的mark word后两位一定是01
     if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) { // 将对象的mark word恢复到加锁之前的状态
        TEVENT (fast_exit: release stacklock) ;
        return;
     }
  }
  // 如果不是上述两种情况,那么该释放重量级监视器
  ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}

inflate函数

inflate静态函数用于返回对象的监视器,其代码如下所示:

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() ; // 对象的mark word
      assert (!mark->has_bias_pattern(), "invariant") ; // 一定不是偏向锁

      // The mark can be in one of the following states:
      // *  Inflated     - just return
      // *  Stack-locked - coerce it to inflated
      // *  INFLATING    - busy wait for conversion to complete
      // *  Neutral      - aggressively inflate the object.
      // *  BIASED       - Illegal.  We should never see this

      // CASE: inflated
      if (mark->has_monitor()) { // 已经是监视器锁状态,mark word已经指向了监视器,即最后两位是10
          ObjectMonitor * inf = mark->monitor() ; // 先前填充过的监视器
          assert (inf->header()->is_neutral(), "invariant");
          assert (inf->object() == object, "invariant") ; // 先前填充过的监视器属于参数object
          assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
          return inf ;
      }

      // CASE: inflation in progress - inflating over a stack-lock.
      // Some other thread is converting from stack-locked to inflated.
      // Only that thread can complete inflation -- other threads must wait.
      // The INFLATING value is transient.
      // Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.
      // We could always eliminate polling by parking the thread on some auxiliary list.
      if (mark == markOopDesc::INFLATING()) { // 正在填充,需要重试,markOopDesc::INFLATING()返回0
         TEVENT (Inflate: spin while INFLATING) ;
         ReadStableMark(object) ;
         continue ;
      }

      // CASE: stack-locked
      // Could be stack-locked either by this thread or by some other thread.
      //
      // Note that we allocate the objectmonitor speculatively, _before_ attempting
      // to install INFLATING into the mark word.  We originally installed INFLATING,
      // allocated the objectmonitor, and then finally STed the address of the
      // objectmonitor into the mark.  This was correct, but artificially lengthened
      // the interval in which INFLATED appeared in the mark, thus increasing
      // the odds of inflation contention.
      //
      // We now use per-thread private objectmonitor free lists.
      // These list are reprovisioned from the global free list outside the
      // critical INFLATING...ST interval.  A thread can transfer
      // multiple objectmonitors en-mass from the global free list to its local free list.
      // This reduces coherency traffic and lock contention on the global free list.
      // Using such local free lists, it doesn't matter if the omAlloc() call appears
      // before or after the CAS(INFLATING) operation.
      // See the comments in omAlloc().

      if (mark->has_locker()) { // 参数object被轻量级锁锁定,这里不需要管是被谁锁定的,因为这个函数只是将轻量锁膨胀成监视器锁
          ObjectMonitor * m = omAlloc (Self) ;
          // 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 ; // _owner是锁记录指针,见1312行
          m->_recursions   = 0 ;
          m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;   // Consider: maintain by type/class

          markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
          if (cmp != mark) {
             omRelease (Self, m, true) ;
             continue ;       // Interference -- just retry
          }

          // We've successfully installed INFLATING (0) into the mark-word.
          // This is the only case where 0 will appear in a mark-work.
          // Only the singular thread that successfully swings the mark-word
          // to 0 can perform (or more precisely, complete) inflation.
          //
          // Why do we CAS a 0 into the mark-word instead of just CASing the
          // mark-word from the stack-locked value directly to the new inflated state?
          // Consider what happens when a thread unlocks a stack-locked object.
          // It attempts to use CAS to swing the displaced header value from the
          // on-stack basiclock back into the object header.  Recall also that the
          // header value (hashcode, etc) can reside in (a) the object header, or
          // (b) a displaced header associated with the stack-lock, or (c) a displaced
          // header in an objectMonitor.  The inflate() routine must copy the header
          // value from the basiclock on the owner's stack to the objectMonitor, all
          // the while preserving the hashCode stability invariants.  If the owner
          // decides to release the lock while the value is 0, the unlock will fail
          // and control will eventually pass from slow_exit() to inflate.  The owner
          // will then spin, waiting for the 0 value to disappear.   Put another way,
          // the 0 causes the owner to stall if the owner happens to try to
          // drop the lock (restoring the header from the basiclock to the object)
          // while inflation is in-progress.  This protocol avoids races that might
          // would otherwise permit hashCode values to change or "flicker" for an object.
          // Critically, while object->mark is 0 mark->displaced_mark_helper() is stable.
          // 0 serves as a "BUSY" inflate-in-progress indicator.


          // fetch the displaced mark from the owner's stack.
          // The owner can't die or unwind past the lock while our INFLATING
          // object is in the mark.  Furthermore the owner can't complete
          // an unlock on the object, either.
          markOop dmw = mark->displaced_mark_helper() ; // 从无锁到轻量级锁时栈桢锁记录的displaced header所保存的参数object的原mark word
          assert (dmw->is_neutral(), "invariant") ; // 上面一行的原mark word一定是无锁的

          // Setup monitor fields to proper values -- prepare the monitor
          m->set_header(dmw) ;

          // Optimization: if the mark->locker stack address is associated
          // with this thread we could simply set m->_owner = Self and
          // m->OwnerIsThread = 1. Note that a thread can inflate an object
          // that it has stack-locked -- as might happen in wait() -- directly
          // with CAS.  That is, we can avoid the xchg-NULL .... ST idiom.
          m->set_owner(mark->locker()); // _owner是参数object的mark word里的锁记录指针
          m->set_object(object);
          // TODO-FIXME: assert BasicLock->dhw != 0.

          // Must preserve store ordering. The monitor state must
          // be stable at the time of publishing the monitor address.
          guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;
          object->release_set_mark(markOopDesc::encode(m));

          // 省略一些代码
          return m ;
      }

      // CASE: neutral
      // TODO-FIXME: for entry we currently inflate and then try to CAS _owner.
      // If we know we're inflating for entry it's better to inflate by swinging a
      // pre-locked objectMonitor pointer into the object header.   A successful
      // CAS inflates the object *and* confers ownership to the inflating thread.
      // In the current implementation we use a 2-step mechanism where we CAS()
      // to inflate and then CAS() again to try to swing _owner from NULL to Self.
      // An inflateTry() method that we could call from fast_enter() and slow_enter()
      // would be useful.

      assert (mark->is_neutral(), "invariant"); // 无锁状态,尚未给参数object分配监视器
      ObjectMonitor * m = omAlloc (Self) ;
      // prepare m for installation - set monitor to initial state
      m->Recycle();
      m->set_header(mark); // 新建的监视器_header字段保存的是无锁时参数object的mark word
      m->set_owner(NULL); // 初值为NULL
      m->set_object(object);
      m->OwnerIsThread = 1 ; // _owner是线程指针
      m->_recursions   = 0 ;
      m->_Responsible  = NULL ;
      m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;       // consider: keep metastats by type/class
      // 使用CAS操作将监视器指针安装到参数object的mark word,若失败则重试
      if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
          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.
      }

      // 省略一些代码
      return m ;
  }
}

根据对象mark word的不同状态,inflate函数有不同的处理方式:

  • 已经是监视器锁状态:直接返回先前的监视器;
  • 正在填充(值为0):重试;
  • 已经被轻量锁锁定:将轻量锁膨胀成监视器;
  • 无锁状态:新建监视器并关联该对象。

获得与释放监视器请见后续文章。

参考文献

[1] David Dice. Implementing Fast Java Monitors with Relaxed-Locks. 2001
[2] JVM同步方法之偏向锁 https://zhuanlan.zhihu.com/p/34662715

推荐阅读更多精彩内容