synchronized深度解析--轻量级锁

496 阅读8分钟

synchronized深度解析 -- 引言

synchronized深度解析--偏向锁

轻量级锁

使用jvm参数-XX:-UseBiasedLocking来关闭偏向锁。

例1: 这里关闭了偏向锁,来直接打打印在同步代码块执行中,和执行后对象头mark word的变化。

public class Test {
    static class A{ }
    public static void main(String[] args) throws Exception {

         A a =  new A();
         synchronized (a){
            System.out.println((ClassLayout.parseInstance(a).toPrintable()));
         }
         System.out.println((ClassLayout.parseInstance(a).toPrintable()));
    }
}
//输出省略了klass pointer 和填充字节,下同
Test$A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           d8 f0 71 02 (11011000 11110000 01110001 00000010) (41021656)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

Test$A object internals:
 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)

回顾前文我们可以知道,当对象头为偏向锁时所标志位为00,指针指向堆栈上的实际头。 jvm的偏向锁开启有延迟,在4s内创建的对象,如果在同步代码块内,那么是轻量级锁而不是偏向锁,退出同步代码块后变成解锁状态(01)。

轻量级锁加锁源码分析

当偏向锁升级程轻量锁锁时,会调用 CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);方法

//这里传入当前的线程,和基础锁对象
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
  //jvm可配置参数,是否需要打印锁统计信息
  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");
  //jvm参数,是否使用偏向锁
  if (UseBiasedLocking) {
    //如果开启了偏向锁,那么这时候就要撤销偏向锁,膨胀为轻量级锁
    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

我们先来看fast_enter,先撤销偏向锁,然后膨胀成轻量级锁的情况。

void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
 //jvm参数,是否使用偏向锁
 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
 slow_enter (obj, lock, THREAD) ;
}

不在安全点直接撤销的情况revoke_and_rebias,根据传参得知attempt_rebias为true.

BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
  assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");

  //获取该对象的mark word部分
  markOop mark = obj->mark();
  //这里判断对象头是否是匿名偏向的,就是没有偏向任何线程。如果是偏向锁撤销膨胀为轻量级锁,那么不可能是匿名的。
  //这里是对于那些匿名偏向的对象,在计算了hashcode之后,会调用该方法进行偏向的撤销。且这时传来的attempt_rebias为false
  if (mark->is_biased_anonymously() && !attempt_rebias) {
  //计算hashcode造成的偏向锁撤销,会尝试不在安全点进行撤销这个操作.
    markOop biased_value       = mark;
    markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
    markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
    if (res_mark == biased_value) {
      return BIAS_REVOKED;
    }
  } else if (mark->has_bias_pattern()) {
  //判断对象是否有偏向模式
    Klass* k = obj->klass();
    markOop prototype_header = k->prototype_header();
    if (!prototype_header->has_bias_pattern()) {
    //这里判断该对象的类是否已经被取消了偏向状态,也就是是否触发了批量撤销
      markOop biased_value       = mark;
      markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);
      assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked");
      return BIAS_REVOKED;
    } else if (prototype_header->bias_epoch() != mark->bias_epoch()) {
    //判断类中的epoch和对象中的一致,如果不一致就是触发了批量重偏向.这时根据参数attempt_rebias进行重偏向还是直接撤销
      if (attempt_rebias) {
      //重偏向
        assert(THREAD->is_Java_thread(), "");
        markOop biased_value       = mark;
        markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());
        markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);
        if (res_mark == biased_value) {
          return BIAS_REVOKED_AND_REBIASED;
        }
      } else {
      //撤销
        markOop biased_value       = mark;
        markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
        markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
        if (res_mark == biased_value) {
          return BIAS_REVOKED;
        }
      }
    }
  }
  //这个方法再偏向锁中分析过,用来统计偏向撤销的次数,根据次数来返回对应的对应的值。
  //如果对象没有偏向模式,或者偏向被取消了,则返回NOT_BIASED。
  //如果刚好达到批量重偏向的阈值,返回 HR_BULK_REBIAS
  //如果是刚好达到批量撤销的阈值,返回HR_BULK_REVOKE.
  //如果仅仅是撤销当前对象的偏向,返回HR_SINGLE_REVOKE
  HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);
  if (heuristics == HR_NOT_BIASED) {
    return NOT_BIASED;
  } else if (heuristics == HR_SINGLE_REVOKE) {
  //拿到该对象的类
    Klass *k = obj->klass();
    //拿到类的对象头
    markOop prototype_header = k->prototype_header();
    if (mark->biased_locker() == THREAD &&
        prototype_header->bias_epoch() == mark->bias_epoch()) {
		//这个if是已经偏向当前线程的对象,执行了object.hash的情况,会进入这个分支.
        //所以匿名偏向的对象还是已经偏向某个线程的对象子计算了hashcode之后都会撤销偏向。
        //这种情况也不需要等待线程安全点,可以直接撤销。
      ResourceMark rm;
      if (TraceBiasedLocking) {
        tty->print_cr("Revoking bias by walking my own stack:");
      }
      EventBiasedLockSelfRevocation event;
      //撤销偏向
      BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD, NULL);
      ((JavaThread*) THREAD)->set_cached_monitor_info(NULL);
      assert(cond == BIAS_REVOKED, "why not?");
      if (event.should_commit()) {
        event.set_lockClass(k);
        event.commit();
      }
      return cond;
    } else {
     //这里就是当前线程不是对象已经偏向的线程,或者epoch过期了。无论是哪种,都需要再线程的安全点执行
      EventBiasedLockRevocation event;
      VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);
      VMThread::execute(&revoke);
      if (event.should_commit() && (revoke.status_code() != NOT_BIASED)) {
        event.set_lockClass(k);
        // Subtract 1 to match the id of events committed inside the safepoint
        event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1);
        event.set_previousOwner(revoke.biased_locker());
        event.commit();
      }
      return revoke.status_code();
    }
  }
  //剩下批量撤销和批量重偏向
  assert((heuristics == HR_BULK_REVOKE) ||
         (heuristics == HR_BULK_REBIAS), "?");
  EventBiasedLockClassRevocation event;
  VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,
                                (heuristics == HR_BULK_REBIAS),
                                attempt_rebias);
  VMThread::execute(&bulk_revoke);
  if (event.should_commit()) {
    event.set_revokedClass(obj->klass());
    event.set_disableBiasing((heuristics != HR_BULK_REBIAS));
    // Subtract 1 to match the id of events committed inside the safepoint
    event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1);
    event.commit();
  }
  return bulk_revoke.status_code();
}

我们再来看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()) {
    //设置轻量级锁的锁头
    lock->set_displaced_header(mark);
    //成功就返回,如果失败就升级重量级锁
    //将BasicLock的地址写入该对象的对象头中,也就是再例1中打印的地址。再解锁的时候同样要在进行一次cas还原
    //其中lock是将要进行替换的值,obj()->mark_addr()是替换的目标地址,mark是预期值
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
      TEVENT (slow_enter: release stacklock) ;
      return ;
    }
    // Fall through to inflate() ...
  } else
  //有锁状态判断是否重入,如果是的话,将锁头设置为NULL。不是重入说明存在竞争,升级重量级锁
  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
	//膨胀为重量级锁
  lock->set_displaced_header(markOopDesc::unused_mark());
  ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}

轻量级锁的解锁

CASE(_monitorexit): {
        //获取当前对象
        oop lockee = STACK_OBJECT(-1);
        //不为空检查
        CHECK_NULL(lockee);
        // derefing's lockee ought to provoke implicit null check
        // find our monitor slot
        BasicObjectLock* limit = istate->monitor_base();
        BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
        //从栈底遍历到栈顶
        while (most_recent != limit ) {
          //找到当前对象的BasicObjectLock
          if ((most_recent)->obj() == lockee) {
          //获取到对象的锁
            BasicLock* lock = most_recent->lock();
            //拿到该对象的mark word,只有再第一次加锁的时候会设置displaced_header的值
            markOop header = lock->displaced_header();
            //将BasicObjectLock中的_obj设置为空,这就相当于释放了锁
            most_recent->set_obj(NULL);
            //如果该对象的mark word 不是偏向模式,也就是说是轻量级锁或者重量级锁
            if (!lockee->mark()->has_bias_pattern()) {
              bool call_vm = UseHeavyMonitors;
              //如果header为空,就说明是重入的锁
              if (header != NULL || call_vm) {
                //这里通过cas将第一次加锁时记录到BasicLock中的displaced_header取回来
                //其中header是将要设置的值,lock是预期值
                if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
                  //如果是重量级锁或者cas失败了,那么要将_obj重新塞回去
                  most_recent->set_obj(lockee);
                  CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);
                }
              }
            }
            UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
          }
          most_recent++;
        }
        // Need to throw illegal monitor state exception
        CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);
        ShouldNotReachHere();
      }

如果不是偏向锁的话会调用CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);

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
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) {
  	//如果为空,说明是重入的情况,这里面没有做实际的操作就返回了
     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() ;
  //用来判断lock中记录的的线程是否是当前的线程,如果不是则膨胀为重量级锁
  if (mark == (markOop) lock) {
     assert (dhw->is_neutral(), "invariant") ;
     //cas失败同样膨胀为重量级锁
     if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
        TEVENT (fast_exit: release stacklock) ;
        return;
     }
  }

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

轻量级锁总结

  1. 轻量级锁和偏向锁设置锁的方式是相同的,第一次加锁会在BasicLock初始化displaced_header。如果是重入的话只会设置BasicObjectLock中的_obj。栈中BasicObjectLock指向该对象的数量代表重入的次数。
  2. 如果该对象需要从偏向锁膨胀到轻量级锁,如么需要先取消偏向。
  3. 偏向锁在加锁和解锁的过程中都要使用cas进行markword的替换,如果失败了会膨胀成重量级锁,再进行相应的加锁或者解锁操作。
  4. 轻量级锁再进入同步代码块后,mark word记录的是BasicLock的地址,退出代码块后会还原成无锁。