注:本篇很长,请找个舒适的姿势阅读。

本文为synchronized系列第二篇。主要内容为分析偏向锁的实现。

偏向锁的诞生背景和基本原理在上文中已经讲过了,强烈建议在有看过上篇文章的基础下阅读本文。

更多文章见个人博客:https://github.com/farmerjohngit/myblog

本系列文章将对HotSpot的synchronized锁实现进行全面分析,内容包括偏向锁、轻量级锁、重量级锁的加锁、解锁、锁升级流程的原理及源码分析,希望给在研究synchronized路上的同学一些帮助。主要包括以下几篇文章:

死磕Synchronized底层实现--概论

死磕Synchronized底层实现--偏向锁

死磕Synchronized底层实现--轻量级锁(待更新)

死磕Synchronized底层实现--重量级锁(待更新)

本文将分为几块内容:

1.偏向锁的入口

2.偏向锁的获取流程

3.偏向锁的撤销流程

4.偏向锁的释放流程

5.偏向锁的批量重偏向和批量撤销

本文分析的JVM版本是JVM8,具体版本号以及代码可以在这里看到。

偏向锁入口

目前网上的很多文章,关于偏向锁源码入口都找错地方了,导致我之前对于偏向锁的很多逻辑一直想不通,走了很多弯路。

synchronized分为synchronized代码块和synchronized方法,其底层获取锁的逻辑都是一样的,本文讲解的是synchronized代码块的实现。上篇文章也说过,synchronized代码块是由monitorentermonitorexit两个指令实现的。

关于HotSpot虚拟机中获取锁的入口,网上很多文章要么给出的方法入口为interpreterRuntime.cpp#monitorenter,要么给出的入口为bytecodeInterpreter.cpp#1816。包括占小狼的这篇文章关于锁入口的位置说法也是有问题的(当然文章还是很好的,在我刚开始研究synchronized的时候,小狼哥的这篇文章给了我很多帮助)。

要找锁的入口,肯定是要在源码中找到对monitorenter指令解析的地方。在HotSpot的中有两处地方对monitorenter指令进行解析:一个是在bytecodeInterpreter.cpp#1816 ,另一个是在templateTable_x86_64.cpp#3667。

前者是JVM中的字节码解释器(bytecodeInterpreter),用C++实现了每条JVM指令(如monitorenterinvokevirtual等),其优点是实现相对简单且容易理解,缺点是执行慢。后者是模板解释器(templateInterpreter),其对每个指令都写了一段对应的汇编代码,启动时将每个指令与对应汇编代码入口绑定,可以说是效率做到了极致。模板解释器的实现可以看这篇文章,在研究的过程中也请教过文章作者‘汪先生’一些问题,这里感谢一下。

在HotSpot中,只用到了模板解释器,字节码解释器根本就没用到,R大的读书笔记中说的很清楚了,大家可以看看,这里不再赘述。

所以montorenter的解析入口在模板解释器中,其代码位于templateTable_x86_64.cpp#3667。通过调用路径:templateTable_x86_64#monitorenter->interp_masm_x86_64#lock_object进入到偏向锁入口macroAssembler_x86#biased_locking_enter,在这里大家可以看到会生成对应的汇编代码。需要注意的是,不是说每次解析monitorenter指令都会调用biased_locking_enter,而是只会在JVM启动的时候调用该方法生成汇编代码,之后对指令的解析是通过直接执行汇编代码。

其实bytecodeInterpreter的逻辑和templateInterpreter的逻辑是大同小异的,因为templateInterpreter中都是汇编代码,比较晦涩,所以看bytecodeInterpreter的实现会便于理解一点。但这里有个坑,在jdk8u之前,bytecodeInterpreter并没有实现偏向锁的逻辑。我之前看的JDK8-87ee5ee27509这个版本就没有实现偏向锁的逻辑,导致我看了很久都没看懂。在这个commit中对bytecodeInterpreter加入了偏向锁的支持,我大致了看了下和templateInterpreter对比除了栈结构不同外,其他逻辑大致相同,所以下文就按bytecodeInterpreter中的代码对偏向锁逻辑进行讲解。templateInterpreter的汇编代码讲解可以看这篇文章,其实汇编源码中都有英文注释,了解了汇编几个基本指令的作用再结合注释理解起来也不是很难。

偏向锁获取流程

下面开始偏向锁获取流程分析,代码在bytecodeInterpreter.cpp#1816。注意本文代码都有所删减。

CASE(_monitorenter): {  // lockee 就是锁对象oop lockee = STACK_OBJECT(-1);  // derefing's lockee ought to provoke implicit null checkCHECK_NULL(lockee);  // code 1:找到一个空闲的Lock RecordBasicObjectLock* limit = istate->monitor_base();BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();BasicObjectLock* entry = NULL;  while (most_recent != limit ) {    if (most_recent->obj() == NULL) entry = most_recent;    else if (most_recent->obj() == lockee) break;most_recent++;}  //entry不为null,代表还有空闲的Lock Recordif (entry != NULL) {    // code 2:将Lock Record的obj指针指向锁对象entry->set_obj(lockee);    int success = false;    uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;    // markoop即对象头的mark wordmarkOop mark = lockee->mark();    intptr_t hash = (intptr_t) markOopDesc::no_hash;    // code 3:如果锁对象的mark word的状态是偏向模式if (mark->has_bias_pattern()) {      uintptr_t thread_ident;      uintptr_t anticipated_bias_locking_value;thread_ident = (uintptr_t)istate->thread();     // code 4:这里有几步操作,下文分析anticipated_bias_locking_value =(((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &~((uintptr_t) markOopDesc::age_mask_in_place);  // code 5:如果偏向的线程是自己且epoch等于class的epochif  (anticipated_bias_locking_value == 0) {        // already biased towards this thread, nothing to doif (PrintBiasedLockingStatistics) {(* BiasedLocking::biased_lock_entry_count_addr())++;}success = true;}       // code 6:如果偏向模式关闭,则尝试撤销偏向锁else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {markOop header = lockee->klass()->prototype_header();        if (hash != markOopDesc::no_hash) {header = header->copy_set_hash(hash);}        // 利用CAS操作将mark word替换为class中的mark wordif (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {          if (PrintBiasedLockingStatistics)(*BiasedLocking::revoked_lock_entry_count_addr())++;}}         // code 7:如果epoch不等于class中的epoch,则尝试重偏向else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {        // 构造一个偏向当前线程的mark wordmarkOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);        if (hash != markOopDesc::no_hash) {new_header = new_header->copy_set_hash(hash);}        // CAS替换对象头的mark word  if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark) == mark) {          if (PrintBiasedLockingStatistics)(* BiasedLocking::rebiased_lock_entry_count_addr())++;}        else {          // 重偏向失败,代表存在多线程竞争,则调用monitorenter方法进行锁升级CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);}success = true;}      else {         // 走到这里说明当前要么偏向别的线程,要么是匿名偏向(即没有偏向任何线程)// code 8:下面构建一个匿名偏向的mark word,尝试用CAS指令替换掉锁对象的mark wordmarkOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |(uintptr_t)markOopDesc::age_mask_in_place |epoch_mask_in_place));        if (hash != markOopDesc::no_hash) {header = header->copy_set_hash(hash);}markOop new_header = (markOop) ((uintptr_t) header | thread_ident);        // debugging hintDEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)        if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {           // CAS修改成功if (PrintBiasedLockingStatistics)(* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;}        else {          // 如果修改失败说明存在多线程竞争,所以进入monitorenter方法CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);}success = true;}}    // 如果偏向线程不是当前线程或没有开启偏向模式等原因都会导致success==falseif (!success) {      // 轻量级锁的逻辑//code 9: 构造一个无锁状态的Displaced Mark Word,并将Lock Record的lock指向它markOop displaced = lockee->mark()->set_unlocked();entry->lock()->set_displaced_header(displaced);      //如果指定了-XX:+UseHeavyMonitors,则call_vm=true,代表禁用偏向锁和轻量级锁bool call_vm = UseHeavyMonitors;      // 利用CAS将对象头的mark word替换为指向Lock Record的指针if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {        // 判断是不是锁重入if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {       //code 10: 如果是锁重入,则直接将Displaced Mark Word设置为nullentry->lock()->set_displaced_header(NULL);} else {          CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);}}}    UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);} else {    // lock record不够,重新执行istate->set_msg(more_monitors);    UPDATE_PC_AND_RETURN(0); // Re-execute}
}

再回顾下对象头中mark word的格式:

JVM中的每个类也有一个类似mark word的prototype_header,用来标记该class的epoch和偏向开关等信息。上面的代码中lockee->klass()->prototype_header()即获取class的prototype_header。

code 1,从当前线程的栈中找到一个空闲的Lock Record(即代码中的BasicObjectLock,下文都用Lock Record代指),判断Lock Record是否空闲的依据是其obj字段 是否为null。注意这里是按内存地址从低往高找到最后一个可用的Lock Record,换而言之,就是找到内存地址最高的可用Lock Record

code 2,获取到Lock Record后,首先要做的就是为其obj字段赋值。

code 3,判断锁对象的mark word是否是偏向模式,即低3位是否为101。

code 4,这里有几步位运算的操作 anticipated_bias_locking_value = (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &  ~((uintptr_t) markOopDesc::age_mask_in_place); 这个位运算可以分为3个部分。

第一部分((uintptr_t)lockee->klass()->prototype_header() | thread_ident) 将当前线程id和类的prototype_header相或,这样得到的值为(当前线程id + prototype_header中的(epoch + 分代年龄 + 偏向锁标志 + 锁标志位)),注意prototype_header的分代年龄那4个字节为0

第二部分 ^ (uintptr_t)mark 将上面计算得到的结果与锁对象的markOop进行异或,相等的位全部被置为0,只剩下不相等的位。

第三部分 & ~((uintptr_t) markOopDesc::age_mask_in_place) markOopDesc::age_mask_in_place为...0001111000,取反后,变成了...1110000111,除了分代年龄那4位,其他位全为1;将取反后的结果再与上面的结果相与,将上面异或得到的结果中分代年龄给忽略掉。

code 5anticipated_bias_locking_value==0代表偏向的线程是当前线程且mark word的epoch等于class的epoch,这种情况下什么都不用做。

code 6(anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0代表class的prototype_header或对象的mark word中偏向模式是关闭的,又因为能走到这已经通过了mark->has_bias_pattern()判断,即对象的mark word中偏向模式是开启的,那也就是说class的prototype_header不是偏向模式。

然后利用CAS指令Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark撤销偏向锁,我们知道CAS会有几个参数,1是预期的原值,2是预期修改后的值 ,3是要修改的对象,与之对应,cmpxchg_ptr方法第一个参数是预期修改后的值,第2个参数是修改的对象,第3个参数是预期原值,方法返回实际原值,如果等于预期原值则说明修改成功。

code 7,如果epoch已过期,则需要重偏向,利用CAS指令将锁对象的mark word替换为一个偏向当前线程且epoch为类的epoch的新的mark word

code 8,CAS将偏向线程改为当前线程,如果当前是匿名偏向则能修改成功,否则进入锁升级的逻辑。

code 9,这一步已经是轻量级锁的逻辑了。从上图的mark word的格式可以看到,轻量级锁中mark word存的是指向Lock Record的指针。这里构造一个无锁状态的mark word,然后存储到Lock RecordLock Record的格式可以看第一篇文章)。设置mark word是无锁状态的原因是:轻量级锁解锁时是将对象头的mark word设置为Lock Record中的Displaced Mark Word,所以创建时设置为无锁状态,解锁时直接用CAS替换就好了。

code 10, 如果是锁重入,则将Lock RecordDisplaced Mark Word设置为null,起到一个锁重入计数的作用。

以上是偏向锁加锁的流程(包括部分轻量级锁的加锁流程),如果当前锁已偏向其他线程||epoch值过期||偏向模式关闭||获取偏向锁的过程中存在并发冲突,都会进入到InterpreterRuntime::monitorenter方法, 在该方法中会对偏向锁撤销和升级。

偏向锁的撤销

这里说的撤销是指在获取偏向锁的过程因为不满足条件导致要将锁对象改为非偏向锁状态;释放是指退出同步块时的过程,释放锁的逻辑会在下一小节阐述。请读者注意本文中撤销与释放的区别。

如果获取偏向锁失败会进入到InterpreterRuntime::monitorenter方法

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))...Handle h_obj(thread, elem->obj());  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),"must be NULL or an object");  if (UseBiasedLocking) {    // Retry fast entry if bias is revoked to avoid unnecessary inflationObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);} else {    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);}...
IRT_END

可以看到如果开启了JVM偏向锁,那会进入到ObjectSynchronizer::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) ;
}

如果是正常的Java线程,会走上面的逻辑进入到BiasedLocking::revoke_and_rebias方法,如果是VM线程则会走到下面的BiasedLocking::revoke_at_safepoint。我们主要看BiasedLocking::revoke_and_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");markOop mark = obj->mark();  if (mark->is_biased_anonymously() && !attempt_rebias) {     //如果是匿名偏向且attempt_rebias==false会走到这里,如锁对象的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();    //code 1: 如果对应class关闭了偏向模式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;    //code2: 如果epoch过期} else if (prototype_header->bias_epoch() != mark->bias_epoch()) {      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;}}}}  //code 3:批量重偏向与批量撤销的逻辑HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);  if (heuristics == HR_NOT_BIASED) {    return NOT_BIASED;} else if (heuristics == HR_SINGLE_REVOKE) {    //code 4:撤销单个线程Klass *k = obj->klass();markOop prototype_header = k->prototype_header();    if (mark->biased_locker() == THREAD &&prototype_header->bias_epoch() == mark->bias_epoch()) {      // 走到这里说明需要撤销的是偏向当前线程的锁,当调用Object#hashcode方法时会走到这一步// 因为只要遍历当前线程的栈就好了,所以不需要等到safepoint再撤销。ResourceMark rm;      if (TraceBiasedLocking) {tty->print_cr("Revoking bias by walking my own stack:");}BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD);((JavaThread*) THREAD)->set_cached_monitor_info(NULL);      assert(cond == BIAS_REVOKED, "why not?");      return cond;} else {      // 下面代码最终会在VM线程中的safepoint调用revoke_bias方法VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);      VMThread::execute(&revoke);      return revoke.status_code();}}   assert((heuristics == HR_BULK_REVOKE) ||(heuristics == HR_BULK_REBIAS), "?");   //code5:批量撤销、批量重偏向的逻辑VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,(heuristics == HR_BULK_REBIAS),attempt_rebias);  VMThread::execute(&bulk_revoke);  return bulk_revoke.status_code();
}

会走到该方法的逻辑有很多,我们只分析最常见的情况:假设锁已经偏向线程A,这时B线程尝试获得锁。

上面的code 1code 2B线程都不会走到,最终会走到code 4处,如果要撤销的锁偏向的是当前线程则直接调用revoke_bias撤销偏向锁,否则会将该操作push到VM Thread中等到safepoint的时候再执行。

关于VM Thread这里介绍下:在JVM中有个专门的VM Thread,该线程会源源不断的从VMOperationQueue中取出请求,比如GC请求。对于需要safepoint的操作(VM_Operationevaluate_at_safepoint返回true)必须要等到所有的Java线程进入到safepoint才开始执行。 关于safepoint可以参考下这篇文章。

接下来我们着重分析下revoke_bias方法。第一个参数为锁对象,第2、3个参数为都为false

static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias, bool is_bulk, JavaThread* requesting_thread) {markOop mark = obj->mark();  // 如果没有开启偏向模式,则直接返回NOT_BIASEDif (!mark->has_bias_pattern()) {...    return BiasedLocking::NOT_BIASED;}  uint age = mark->age();  // 构建两个mark word,一个是匿名偏向模式(101),一个是无锁模式(001)markOop   biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age);markOop unbiased_prototype = markOopDesc::prototype()->set_age(age);...JavaThread* biased_thread = mark->biased_locker();  if (biased_thread == NULL) {     // 匿名偏向。当调用锁对象的hashcode()方法可能会导致走到这个逻辑// 如果不允许重偏向,则将对象的mark word设置为无锁模式if (!allow_rebias) {obj->set_mark(unbiased_prototype);}...    return BiasedLocking::BIAS_REVOKED;}  // code 1:判断偏向线程是否还存活bool thread_is_alive = false;  // 如果当前线程就是偏向线程 if (requesting_thread == biased_thread) {thread_is_alive = true;} else {     // 遍历当前jvm的所有线程,如果能找到,则说明偏向的线程还存活for (JavaThread* cur_thread = Threads::first(); cur_thread != NULL; cur_thread = cur_thread->next()) {      if (cur_thread == biased_thread) {thread_is_alive = true;        break;}}}  // 如果偏向的线程已经不存活了if (!thread_is_alive) {    // 允许重偏向则将对象mark word设置为匿名偏向状态,否则设置为无锁状态if (allow_rebias) {obj->set_mark(biased_prototype);} else {obj->set_mark(unbiased_prototype);}...    return BiasedLocking::BIAS_REVOKED;}  // 线程还存活则遍历线程栈中所有的Lock RecordGrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);BasicLock* highest_lock = NULL;  for (int i = 0; i < cached_monitor_info->length(); i++) {MonitorInfo* mon_info = cached_monitor_info->at(i);    // 如果能找到对应的Lock Record说明偏向的线程还在执行同步代码块中的代码if (mon_info->owner() == obj) {...      // 需要升级为轻量级锁,直接修改偏向线程栈中的Lock Record。为了处理锁重入的case,在这里将Lock Record的Displaced Mark Word设置为null,第一个Lock Record会在下面的代码中再处理markOop mark = markOopDesc::encode((BasicLock*) NULL);highest_lock = mon_info->lock();highest_lock->set_displaced_header(mark);} else {...}}  if (highest_lock != NULL) {    // 修改第一个Lock Record为无锁状态,然后将obj的mark word设置为执行该Lock Record的指针highest_lock->set_displaced_header(unbiased_prototype);obj->release_set_mark(markOopDesc::encode(highest_lock));...} else {    // 走到这里说明偏向线程已经不在同步块中了...    if (allow_rebias) {       //设置为匿名偏向状态obj->set_mark(biased_prototype);} else {      // 将mark word设置为无锁状态obj->set_mark(unbiased_prototype);}}  return BiasedLocking::BIAS_REVOKED;
}

需要注意下,当调用锁对象的Object#hashSystem.identityHashCode()方法会导致该对象的偏向锁或轻量级锁升级。这是因为在Java中一个对象的hashcode是在调用这两个方法时才生成的,如果是无锁状态则存放在mark word中,如果是重量级锁则存放在对应的monitor中,而偏向锁是没有地方能存放该信息的,所以必须升级。具体可以看这篇文章的hashcode()方法对偏向锁的影响小节(注意:该文中对于偏向锁的加锁描述有些错误),另外我也向该文章作者请教过一些问题,他很热心的回答了我,在此感谢一下!

言归正传,revoke_bias方法逻辑:

  1. 查看偏向的线程是否存活,如果已经不存活了,则直接撤销偏向锁。JVM维护了一个集合存放所有存活的线程,通过遍历该集合判断某个线程是否存活。

  2. 偏向的线程是否还在同步块中,如果不在了,则撤销偏向锁。我们回顾一下偏向锁的加锁流程:每次进入同步块(即执行monitorenter)的时候都会以从高往低的顺序在栈中找到第一个可用的Lock Record,将其obj字段指向锁对象。每次解锁(即执行monitorexit)的时候都会将最低的一个相关Lock Record移除掉。所以可以通过遍历线程栈中的Lock Record来判断线程是否还在同步块中。

  3. 将偏向线程所有相关Lock RecordDisplaced Mark Word设置为null,然后将最高位的Lock RecordDisplaced Mark Word 设置为无锁状态,最高位的Lock Record也就是第一次获得锁时的Lock Record(这里的第一次是指重入获取锁时的第一次),然后将对象头指向最高位的Lock Record,这里不需要用CAS指令,因为是在safepoint。 执行完后,就升级成了轻量级锁。原偏向线程的所有Lock Record都已经变成轻量级锁的状态。这里如果看不明白,请再回顾游戏啊上篇文章的轻量级锁加锁过程。

偏向锁的释放

偏向锁的释放入口在bytecodeInterpreter.cpp#1923

CASE(_monitorexit): {oop lockee = STACK_OBJECT(-1);  CHECK_NULL(lockee);  // derefing's lockee ought to provoke implicit null check// find our monitor slotBasicObjectLock* limit = istate->monitor_base();BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();  // 从低往高遍历栈的Lock Recordwhile (most_recent != limit ) {    // 如果Lock Record关联的是该锁对象if ((most_recent)->obj() == lockee) {BasicLock* lock = most_recent->lock();markOop header = lock->displaced_header();      // 释放Lock Recordmost_recent->set_obj(NULL);      // 如果是偏向模式,仅仅释放Lock Record就好了。否则要走轻量级锁or重量级锁的释放流程if (!lockee->mark()->has_bias_pattern()) {        bool call_vm = UseHeavyMonitors;        // header!=NULL说明不是重入,则需要将Displaced Mark Word CAS到对象头的Mark Wordif (header != NULL || call_vm) {          if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {            // CAS失败或者是重量级锁则会走到这里,先将obj还原,然后调用monitorexit方法most_recent->set_obj(lockee);            CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);}}}      //执行下一条命令UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);}    //处理下一条Lock Recordmost_recent++;}  // Need to throw illegal monitor state exceptionCALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);  ShouldNotReachHere();
}

上面的代码结合注释理解起来应该不难,偏向锁的释放很简单,只要将对应Lock Record释放就好了,而轻量级锁则需要将Displaced Mark Word替换到对象头的mark word中。如果CAS失败或者是重量级锁则进入到InterpreterRuntime::monitorexit方法中。该方法会在轻量级与重量级锁的文章中讲解。

批量重偏向和批量撤销

批量重偏向和批量撤销的背景可以看上篇文章,相关实现在BiasedLocking::revoke_and_rebias中:

BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {...  //code 1:重偏向的逻辑HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);  // 非重偏向的逻辑...      assert((heuristics == HR_BULK_REVOKE) ||(heuristics == HR_BULK_REBIAS), "?");   //code 2:批量撤销、批量重偏向的逻辑VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,(heuristics == HR_BULK_REBIAS),attempt_rebias);  VMThread::execute(&bulk_revoke);  return bulk_revoke.status_code();
}

在每次撤销偏向锁的时候都通过update_heuristics方法记录下来,以类为单位,当某个类的对象撤销偏向次数达到一定阈值的时候JVM就认为该类不适合偏向模式或者需要重新偏向另一个对象,update_heuristics就会返回HR_BULK_REVOKEHR_BULK_REBIAS。进行批量撤销或批量重偏向。

先看update_heuristics方法。

static HeuristicsResult update_heuristics(oop o, bool allow_rebias) {markOop mark = o->mark();  //如果不是偏向模式直接返回if (!mark->has_bias_pattern()) {    return HR_NOT_BIASED;} // 锁对象的类Klass* k = o->klass();  // 当前时间jlong cur_time = os::javaTimeMillis();  // 该类上一次批量撤销的时间jlong last_bulk_revocation_time = k->last_biased_lock_bulk_revocation_time();  // 该类偏向锁撤销的次数int revocation_count = k->biased_lock_revocation_count();  // BiasedLockingBulkRebiasThreshold是重偏向阈值(默认20),BiasedLockingBulkRevokeThreshold是批量撤销阈值(默认40),BiasedLockingDecayTime是开启一次新的批量重偏向距离上次批量重偏向的后的延迟时间,默认25000。也就是开启批量重偏向后,经过了一段较长的时间(>=BiasedLockingDecayTime),撤销计数器才超过阈值,那我们会重置计数器。if ((revocation_count >= BiasedLockingBulkRebiasThreshold) &&(revocation_count <  BiasedLockingBulkRevokeThreshold) &&(last_bulk_revocation_time != 0) &&(cur_time - last_bulk_revocation_time >= BiasedLockingDecayTime)) {    // This is the first revocation we've seen in a while of an// object of this type since the last time we performed a bulk// rebiasing operation. The application is allocating objects in// bulk which are biased toward a thread and then handing them// off to another thread. We can cope with this allocation// pattern via the bulk rebiasing mechanism so we reset the// klass's revocation count rather than allow it to increase// monotonically. If we see the need to perform another bulk// rebias operation later, we will, and if subsequently we see// many more revocation operations in a short period of time we// will completely disable biasing for this type.k->set_biased_lock_revocation_count(0);revocation_count = 0;}  // 自增撤销计数器if (revocation_count <= BiasedLockingBulkRevokeThreshold) {revocation_count = k->atomic_incr_biased_lock_revocation_count();}  // 如果达到批量撤销阈值则返回HR_BULK_REVOKEif (revocation_count == BiasedLockingBulkRevokeThreshold) {    return HR_BULK_REVOKE;}  // 如果达到批量重偏向阈值则返回HR_BULK_REBIASif (revocation_count == BiasedLockingBulkRebiasThreshold) {    return HR_BULK_REBIAS;}  // 没有达到阈值则撤销单个对象的锁return HR_SINGLE_REVOKE;
}

当达到阈值的时候就会通过VM 线程在safepoint调用bulk_revoke_or_rebias_at_safepoint, 参数bulk_rebias如果是true代表是批量重偏向否则为批量撤销。attempt_rebias_of_object代表对操作的锁对象o是否运行重偏向,这里是true

static BiasedLocking::Condition bulk_revoke_or_rebias_at_safepoint(oop o,                                                                   bool bulk_rebias,                                                                   bool attempt_rebias_of_object,JavaThread* requesting_thread) {...jlong cur_time = os::javaTimeMillis();o->klass()->set_last_biased_lock_bulk_revocation_time(cur_time);Klass* k_o = o->klass();Klass* klass = k_o;  if (bulk_rebias) {    // 批量重偏向的逻辑if (klass->prototype_header()->has_bias_pattern()) {      // 自增前类中的的epochint prev_epoch = klass->prototype_header()->bias_epoch();      // code 1:类中的epoch自增klass->set_prototype_header(klass->prototype_header()->incr_bias_epoch());      int cur_epoch = klass->prototype_header()->bias_epoch();      // code 2:遍历所有线程的栈,更新类型为该klass的所有锁实例的epochfor (JavaThread* thr = Threads::first(); thr != NULL; thr = thr->next()) {GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(thr);        for (int i = 0; i < cached_monitor_info->length(); i++) {MonitorInfo* mon_info = cached_monitor_info->at(i);oop owner = mon_info->owner();markOop mark = owner->mark();          if ((owner->klass() == k_o) && mark->has_bias_pattern()) {            // We might have encountered this object already in the case of recursive lockingassert(mark->bias_epoch() == prev_epoch || mark->bias_epoch() == cur_epoch, "error in bias epoch adjustment");owner->set_mark(mark->set_bias_epoch(cur_epoch));}}}}    // 接下来对当前锁对象进行重偏向revoke_bias(o, attempt_rebias_of_object && klass->prototype_header()->has_bias_pattern(), true, requesting_thread);} else {...    // code 3:批量撤销的逻辑,将类中的偏向标记关闭,markOopDesc::prototype()返回的是一个关闭偏向模式的prototypeklass->set_prototype_header(markOopDesc::prototype());    // code 4:遍历所有线程的栈,撤销该类所有锁的偏向for (JavaThread* thr = Threads::first(); thr != NULL; thr = thr->next()) {GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(thr);      for (int i = 0; i < cached_monitor_info->length(); i++) {MonitorInfo* mon_info = cached_monitor_info->at(i);oop owner = mon_info->owner();markOop mark = owner->mark();        if ((owner->klass() == k_o) && mark->has_bias_pattern()) {          revoke_bias(owner, false, true, requesting_thread);}}}    // 撤销当前锁对象的偏向模式revoke_bias(o, false, true, requesting_thread);}...BiasedLocking::Condition status_code = BiasedLocking::BIAS_REVOKED;  if (attempt_rebias_of_object &&o->mark()->has_bias_pattern() &&klass->prototype_header()->has_bias_pattern()) {    // 构造一个偏向请求线程的mark wordmarkOop new_mark = markOopDesc::encode(requesting_thread, o->mark()->age(),klass->prototype_header()->bias_epoch());    // 更新当前锁对象的mark wordo->set_mark(new_mark);status_code = BiasedLocking::BIAS_REVOKED_AND_REBIASED;...}...  return status_code;
}

该方法分为两个逻辑:批量重偏向和批量撤销。

先看批量重偏向,分为两步:

code 1 将类中的撤销计数器自增1,之后当该类已存在的实例获得锁时,就会尝试重偏向,相关逻辑在偏向锁获取流程小节中。

code 2 处理当前正在被使用的锁对象,通过遍历所有存活线程的栈,找到所有正在使用的偏向锁对象,然后更新它们的epoch值。也就是说不会重偏向正在使用的锁,否则会破坏锁的线程安全性。

批量撤销逻辑如下:

code 3将类的偏向标记关闭,之后当该类已存在的实例获得锁时,就会升级为轻量级锁;该类新分配的对象的mark word则是无锁模式。

code 4处理当前正在被使用的锁对象,通过遍历所有存活线程的栈,找到所有正在使用的偏向锁对象,然后撤销偏向锁。

死磕Synchronized底层实现--偏向锁相关推荐

  1. yield方法释放锁吗_死磕Synchronized底层实现重量级锁

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:farmerjohngit 链接:https://github.com/farmer ...

  2. 死磕Synchronized底层实现

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 来源:http://suo.im/6h5g96 关于syn ...

  3. 死磕 synchronized 底层实现

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达今日推荐:2020年7月程序员工资统计,平均14357元,又跌了,扎心个人原创100W+访问量博客:点击前往,查看更多 来源 ...

  4. ✨Synchronized底层实现---偏向锁

    偏向锁入口 synchronized分为synchronized代码块和synchronized方法,其底层获取锁的逻辑都是一样的. 要找到锁的入口,就要找到代码中对monitorenter指令解析的 ...

  5. Synchronized原理(偏向锁篇)

    Synchronized原理(偏向锁篇) 传统的锁机制 传统的锁依赖于系统的同步函数,在linux上使用mutex互斥锁,最底层实现依赖于futex,这些同步函数都涉及到用户态和内核态的切换.进程的上 ...

  6. 死磕synchronized五:系统剖析轻量级锁

    哈喽,大家好,我是江湖人送外号[道格牙]的子牙老师. 近期准备写一个专栏:从Hotspot源码角度剖析synchronized.前前后后大概有10篇,会全网发,写完后整理成电子书放公众号供大家下载.对 ...

  7. 学习笔记day12 synchronized底层实现及锁升级机制

    原博客:https://blog.csdn.net/weixin_40394952/article/details/118693945 一.synchronized使用方法 1.修饰实例方法,对当前实 ...

  8. 面试官:说一下Synchronized底层实现,锁升级的具体过程?

    介绍 这是我去年7,8月份面试的时候被问的一个面试题,说实话被问到这个问题还是很意外的,感觉这个东西没啥用啊,直到后面被问了一波new Object,Integer对象等作为加锁对象行吗?会出现哪些问 ...

  9. 死磕java底层(一)—多线程

    1.线程和进程 1.1线程和进程的区别 进程 它是内存中的一段独立的空间,可以负责当前应用程序的运行.当前这个进程负责调度当前程序中的所有运行细节(操作系统为进程分配一块独立的运行空间): 线程 它是 ...

最新文章

  1. 获得PMP证书的这一年
  2. CascadePSP 测试笔记
  3. 一场惊心动魄的国际黑客入侵保卫战
  4. 三角函数和复指数函数的转化_三角函数与复数
  5. angularjs $state.go页面不刷新数据
  6. 阿里云链接ftp报错: 20 秒后无活动,连接超时 错误:无法连接到服务器
  7. Unity 内置渲染管线、SRP、URP、HDRP区别
  8. 虚拟机桥接模式下配置静态IP
  9. 深度:企业为什么需要一个平台级的OA产品?
  10. 静态路由 动态路由 php,静态路由汇总(路由聚合)
  11. 玩家如何在游戏中使用辅助脚本
  12. 深度学习中常见的打标签工具和数据集集合(转)
  13. 响度与响度处理经验谈(上)响度测量
  14. Silvaco TCAD仿真8——网格mesh的意义(举例说明)
  15. Spring(入门)
  16. 百钱百鸡问题(C++枚举法)
  17. 【给自己看的笔记,随便写写】如何去调整游戏数值(新手为例)
  18. 鸿蒙OS与安卓、Fuchsia 对比分析
  19. Handler基本使用(二)new Message 、 Handler.obtainMessage和Message.obtain
  20. Upload to server password failed. Failed to transfer file . Permission denied.

热门文章

  1. 10.02 T3 打表找递推式+十进制快速幂 九校联考凉心模拟DAY1T1
  2. ftp安装和虚拟用户创建(终于搞清楚了)
  3. 程序员辞职卖卷饼:4天挣1个月工资!摆摊真那么赚钱?
  4. 大容量导入或导出的数据格式 -- Unicode字符格式
  5. 实现将一个字符串转化成对应的整形数字
  6. vim去掉windows文本的多余的回车符(^M)
  7. Query String Object 2.1.7
  8. 数据回归分析和拟合的Matlab实现
  9. 基于农业物联网的感知数据获取和可视化系统
  10. linux下开启程序崩溃生成core文件开关之ulimit详解