Java并发系列(15)——synchronized之HotSpot源码解读(中)

上一篇:《Java并发系列(14)——synchronized之HotSpot源码解读(上)

10.5 轻量锁

10.5.1 偏向锁升级轻量锁

偏向锁针对的场景是,从头到尾只有一个线程对锁对象加锁。这种场景下 synchronized 性能损耗可以忽略不计,这也是为什么在前面的测试中看到,单线程情况下 Hashtable 有着可以比肩 HashMap 的性能。

而一旦出现多个线程对同一个对象加锁,这个对象就会从偏向锁升级(膨胀)为轻量锁。这个操作又被称为偏向撤销。

用 Java 代码来表达这个场景:

//thread1 先来
Thread thread1 = new Thread (() -> {
   
    synchronized (lock) {
   
        //...
    }
});
thread1.start();
thread1.join();
//以上执行完,lock 已经偏向 thread1
//当前线程也来对 lock 加锁,会触发偏向撤销
synchronized (lock) {
   
    //...
}
//以上执行完,lock 膨胀为轻量锁

这个加锁流程,首先还是 monitorenter 指令关于偏向锁的几个判断:

  • 是否开启偏向锁?肯定是开启的;
  • 是否偏向自己?肯定不是;
  • 是否可偏向?当然可以,现在还是 101 状态,只不过偏向其它线程;
  • 偏向是否过期?偏向过期后面再讲,这里还不会过期;
  • 进入最后一个 else 判断,与偏向锁第一次加锁一样。

也就是下面这块代码:

在这里插入图片描述

这里与偏向锁第一次加锁的区别是:

  • 偏向锁第一次加锁,mark word 是匿名偏向(00…00101),cas 操作会成功,success = true,加锁完成;
  • 而这里的场景,锁对象已经偏向其它线程了,所以 cas 会失败。

于是这里就进入了 InterpreterRuntime::monitorenter(THREAD, entry) 方法:

在这里插入图片描述

如果开启了偏向锁,就会走 fast_enter 方法,进一步会走到 revoke_and_rebias 方法:

在这里插入图片描述

上图 HotSpot 源码中我已经加了中文注释,最后会创建一个 VM_RevokeBias 事件,VMThread 将会执行这个事件。

可以猜到这里应该是一个策略模式,具体执行逻辑应该还是定义在 VM_RevokeBias 里面,这里第一次我们还是尝试跟踪一下。

在这里插入图片描述

无论如何,总是要执行 evaluate 方法,继续跟踪:

在这里插入图片描述

接下来去找 VM_RevokeBias 事件的 doit 方法:

在这里插入图片描述

不出所料,果然又回到了 VM_RevokeBias 类,接下来调用 revoke_bias 方法。这个方法非常关键,也很长很复杂,这里把源码贴出来,仅保留主流程:

static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias, bool is_bulk, JavaThread* requesting_thread, JavaThread** biased_locker) {
   
  markOop mark = obj->mark();
  //1.如果锁对象原本就不可偏向,走这里直接返回
  if (!mark->has_bias_pattern()) {
   
    //...
    return BiasedLocking::NOT_BIASED;
  }

  uint age = mark->age();
  //2.计算匿名偏向的 mark word,即 00...00101
  markOop   biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age);
  //3.计算无锁不可偏向的 mark word,即 ...001
  markOop unbiased_prototype = markOopDesc::prototype()->set_age(age);
  //...
  
  //4.当前偏向的线程
  JavaThread* biased_thread = mark->biased_locker();
  //5.如果原来是匿名偏向,走这里,比如计算了 hash code,把锁对象 mark word 改为无锁不可偏向
  if (biased_thread == NULL) {
   
    // Object is anonymously biased. We can get here if, for
    // example, we revoke the bias due to an identity hash code
    // being computed for an object.
    if (!allow_rebias) {
   
      obj->set_mark(unbiased_prototype);
    }
    return BiasedLocking::BIAS_REVOKED;
  }

  // Handle case where the thread toward which the object was biased has exited
  //6.判断偏向的线程是否还活着
  bool thread_is_alive = false;
  //7.如果当前加锁的线程 == 偏向线程,那么一定活着
  if (requesting_thread == biased_thread) {
   
    thread_is_alive = true;
  }
  //8.否则,遍历所有存活线程,查看偏向线程是否还活着
  else {
   
    for (JavaThread* cur_thread = Threads::first(); cur_thread != NULL; cur_thread = cur_thread->next()) {
   
      if (cur_thread == biased_thread) {
   
        thread_is_alive = true;
        break;
      }
    }
  }
  //9.如果偏向线程已经死了
  if (!thread_is_alive) {
   
    //10.如果可以重偏向,把锁对象的 mark word 改为匿名偏向
    //偏向锁膨胀为轻量锁的场景,调用方传入的是 false
    if (allow_rebias) {
   
      obj->set_mark(biased_prototype);
    }
    //11.如果不可以重偏向,把锁对象的 mark word 改为无锁不可偏向
    else {
   
      obj->set_mark(unbiased_prototype);
    }
    //...
    return BiasedLocking::BIAS_REVOKED;
  }

  // Thread owning bias is alive.
  // Check to see whether it currently owns the lock and, if so,
  // write down the needed displaced headers to the thread's stack.
  // Otherwise, restore the object's header either to the unlocked
  // or unbiased state.
  GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);
  BasicLock* highest_lock = NULL;
  //12.如果偏向线程还活着,会走到这里,遍历偏向线程栈中的所有 lock record(MonitorInfo 存储的信息与 lock record 差别不大)
  for (int i = 0; i < cached_monitor_info->length(); i++) {
   
    MonitorInfo* mon_info = cached_monitor_info->at(i);
    //13.如果还有 lock record 指向锁对象,表明偏向线程还没有退出 synchronized 代码块,因为 monitorexit 时会把这里的指针断开
    if (mon_info->owner() == obj) {
   
      //...
      // Assume recursive case and fix up highest lock later
      //将 lock record 中的 mark word 置空
      markOop mark = markOopDesc::encode((BasicLock*) NULL);
      highest_lock = mon_info->lock();
      highest_lock->set_displaced_header(mark);
    }
    //...
  }
  //14.如果偏向线程还没有退出 synchronized
  if (highest_lock != NULL) {
   
    // Fix up highest lock to contain displaced header and point
    // object at it
    //15.lock record 的 mark word 设置为不可偏向无锁
    highest_lock->set_displaced_header(unbiased_prototype);
    // Reset object header to point to displaced mark.
    // Must release storing the lock address for platforms without TSO
    // ordering (e.g. ppc).
    //16.锁对象的 mark word 中存入 lock record 的指针
    obj->release_set_mark(markOopDesc::encode(highest_lock));
    //...
  }
  //17.如果偏向线程已经退出 synchronized,与偏向线程已经死掉的情况做一样的操作
  else {
   
    //...
  }

  //...
  return BiasedLocking::BIAS_REVOKED;
}

虽然已经在源码里面加了注释,还是再梳理一下。

撤销偏向锁的处理,包括以下几种情况:

  • 根据锁对象 mark word,原本就不可偏向:什么都不用做;
  • 如果可偏向但没有偏向:锁对象 mark word 改为无锁(001);
  • 如果偏向线程还没有退出 synchronized:把它所有的 lock record 的 mark word 清除,并把最高位的那个 lock record 的 mark word 改为无锁,锁对象的 mark word 里面存入指向 lock record 的指针;
  • 如果偏向线程已经退出 synchronized:如果还能偏向就把锁对象 mark word 改为匿名偏向(101),否则改为无锁;
  • 如果偏向线程已经死了,同上。

偏向线程还没有退出 synchronized 块的情况暂时先不讲,这里讲的场景是已经退出 synchronized(见上面 Java 代码),并且实际上应该已经死掉了。

在这里插入图片描述

如上图所示,在第二个线程试图 synchronized 同一个锁对象,触发撤销偏向锁之前,锁对象的 mark word 记录的还是原偏向线程的 id 和 101,当然还有分代年龄之类的其它一些东西。原偏向线程因为已经退出 synchronized,所以它的线程栈里面没有任何 lock record 还指向锁对象(在偏向锁释放锁时讲过)。

既然原偏向线程已经不持有锁了,那偏向锁撤销就很简单,直接把锁对象 mark word 改为不可偏向的无锁状态(即 001,也可以说是轻量锁无锁)就行。

好了,到这一步,已经把原来的偏向锁撤销了,但是还没完,现在要开始对锁对象加锁。

注意上面 revoke_bias 方法的返回值是:BiasedLocking::BIAS_REVOKED。

现在源码返回到 ObjectSynchronizer::fast_enter 方法:

在这里插入图片描述

因为 revoke_and_rebias 方法返回的是 BiasedLocking::BIAS_REVOKED,所以不会从上面 return 出去,会进入 slow_enter 方法:
在这里插入图片描述

因为之前偏向锁撤销的时候,把锁对象的 mark word 改成了无锁,所以这里会走第一个 if,然后把 lock record 里面的 mark word 改成锁对象的无锁 mark word,再把锁对象的无锁 mark word 改成一个指向 lock record 里面 mark word(实际是 BasicLock 类型)的指针。但这个指针最后两位永远是 00,表示轻量锁,不清楚是怎么做到的。

最终轻量锁加锁完成就会变成下面这样:

在这里插入图片描述

10.5.2 释放锁

轻量锁释放,再去看 monitorexit 指令的实现:

在这里插入图片描述

轻量锁释放锁就比较简单了:

  • 先把 lock record 指向锁对象的指针断开;
  • 如果是重入的锁,到此结束;
  • 如果是退出最后一个 synchronized 真正要释放锁了,就把 lock record 里面记录的 mark word 再还原到锁对象的 mark word 里面去。

可以看出,轻量锁释放锁性能比偏向锁稍差,多做一个 cas。

10.5.3 无锁直接加轻量锁

这里的场景是,开始就是无锁(001)——比如禁用了偏向锁或偏向锁已经膨胀为轻量锁——然后对无锁加轻量锁。

还是看 monitorenter 指令入口:

 CASE(_monitorenter): {
   
        oop lockee = STACK_OBJECT(-1);
        // derefing's lockee ought to provoke implicit null check
        CHECK_NULL(lockee);
        // find a free monitor or one already allocated for this object
        // if we find a matching object then we need a new monitor
        // since this is recursive enter
        BasicObjectLock* 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++;
        }
        if (entry != NULL) {
   
          entry->set_obj(lockee);
          int success = false;
          uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;

          markOop mark = lockee->mark();
          intptr_t hash = (intptr_t) markOopDesc::no_hash;
          // implies UseBiasedLocking
          if (mark->has_bias_pattern()) {
   
              //...
          }
           // traditional lightweight locking
          //非偏向锁进这里
          if (!success) {
   
            //计算一个 001 mark word
            markOop displaced = lockee->mark()->set_unlocked();
            //lock record 里面的 mark word 设为上面 001 的 mark word
            entry->lock()->set_displaced_header(displaced);
            //JVM 指令:-XX:+UseHeavyMonitors
            bool call_vm = UseHeavyMonitors;
            //cas,如果锁对象的 mark word 是 001,将其替换为 lock record 指针,cas 失败就进去
            if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
   
              // Is it simple recursive case?
              //上面 cas 失败,如果只是重入,把 lock record 里面的 mark word 再去掉
              if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
   
                entry->lock()->set_displaced_header(
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值