jvm源码解读--17 Java的wait()、notify()学习

write and debug by 张艳涛

wait()和notify()的通常用法

  1. A线程取得锁,执行wait(),释放锁;
  2. B线程取得锁,完成业务后执行notify(),再释放锁;
  3. B线程释放锁之后,A线程取得锁,继续执行wait()之后的代码;

关于synchronize修饰的代码块
通常,对于synchronize(lock){…}这样的代码块,编译后会生成monitorenter和monitorexit指令,线程执行到monitorenter指令时会尝试取得lock对应的monitor的所有权(CAS设置对象头),取得后即获取到锁,执行monitorexit指令时会释放monitor的所有权即释放锁;

一个完整的demo
为了深入学习wait()和notify(),先用完整的demo程序来模拟场景吧,以下是源码:

package com.zyt.wait_notify;

public class NotifyDemo {

    private static void sleep(long sleepVal){
        try{
            Thread.sleep(sleepVal);
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    private static void log(String desc){
        System.out.println(Thread.currentThread().getName() + " : " + desc);
    }

    Object lock = new Object();

    public void startThreadA(){
        new Thread(() -> {
            synchronized (lock){
                log("get lock");
                startThreadB();
                log("start wait");
                try {
                    
                }catch(InterruptedException e){
                    e.printStackTrace();
                }

                log("get lock after wait");
                log("release lock");
            }
        }, "thread-A").start();
    }

    public void startThreadB(){
        new Thread(()->{
            synchronized (lock){
                log("get lock");
                startThreadC();
                sleep(100);
                log("start notify");
                lock.notify();
                log("release lock");

            }
        },"thread-B").start();
    }

    public void startThreadC(){
        new Thread(() -> {
            synchronized (lock){
                log("get lock");
                log("release lock");
            }
        }, "thread-C").start();
    }

    public static void main(String[] args){
        new NotifyDemo().startThreadA();
    }
}

 

 

以上就是本次实战用到的demo,代码功能简述如下:

启动线程A,取得锁之后先启动线程B再执行wait()方法,释放锁并等待;
线程B启动之后会等待锁,A线程执行wait()之后,线程B取得锁,然后启动线程C,再执行notify唤醒线程A,最后退出synchronize代码块,释放锁;
线程C启动之后就一直在等待锁,这时候线程B还没有退出synchronize代码块,锁还在线程B手里;
线程A在线程B执行notify()之后就一直在等待锁,这时候线程B还没有退出synchronize代码块,锁还在线程B手里;
线程B退出synchronize代码块,释放锁之后,线程A和线程C竞争锁;
把上面的代码在Openjdk8下面执行,反复执行多次,都得到以下结果:

thread-A : get lock
thread-A : start wait
thread-B : get lock
thread-C : c thread is start
thread-B : start notify
thread-B : release lock
thread-A : after wait, acquire lock again
thread-A : release lock
thread-C : get lock
thread-C : release lock

针对以上结果,问题来了:
第一个问题:
将以上代码反复执行多次,结果都是B释放锁之后A会先得到锁,这又是为什么呢?C为何不能先拿到锁呢?

第二个问题:
线程C自开始就执行了monitorenter指令,它能得到锁是容易理解的,但是线程A呢?在wait()之后并没有没有monitorenter指令,那么它又是如何取得锁的呢?

wait()、notify()这些方法都是native方法,所以只有从JVM源码寻找答案了,本次阅读的是openjdk8的源码;

带上问题去看JVM源码
按照demo代码执行顺序,我整理了如下问题,带着这些问题去看JVM源码可以聚焦主线,不要被一些支线的次要的代码卡住(例如一些异常处理,监控和上报等):

线程A在wait()的时候做了什么?
线程C启动后,由于此时线程B持有锁,那么线程C此时在干啥?
线程B在notify()的时候做了什么?
线程B释放锁的时候做了什么?

好了,接下来看源码分析问题吧:

线程A在wait()的时候做了什么

java代码中的

lock.wait();

这个lock是一个object对象

如果你看源码的时候能看到这个是一个native方法,jvm对于native方法的处理有俩种方法,

  1. 一种是自定义,使用javah 生成对应的.hpp的头,在用c++写对于的.cpp方法实现,其中会调用jin.h的env方法,在使用gcc编译成.so文件,

 

ObjTest.java

package jni;
class A {}
public class ObjTest extends A{
    static {
        System.loadLibrary("ObjTest");
    }
    public ObjTest(){
        System.out.println("default");
    }
    public ObjTest(int age){
        System.out.println("param Construtor,age->"+age);
    }
    public native static void test(Object a);

    public static void main(String[] args){
        test(new ObjTest());
    }
}

 

  2.另外的一种就是系统自带的native,

如果你要看代码的话


#include <stdio.h>
#include <signal.h>
#include <limits.h>

#include "jni.h"
#include "jni_util.h"
#include "jvm.h"

#include "java_lang_Object.h"

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

JNIEXPORT jclass JNICALL
Java_java_lang_Object_getClass(JNIEnv *env, jobject this)
{
    if (this == NULL) {
        JNU_ThrowNullPointerException(env, NULL);
        return 0;
    } else {
        return (*env)->GetObjectClass(env, this);
    }
}

会发现它使用了 RegisterNatives这种方法,wait对于的方法,为   (void *)&JVM_MonitorWait

在share/vm/prims/jvm.cpp文件中定义的

JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
  JVMWrapper("JVM_MonitorWait");
  Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
  JavaThreadInObjectWaitState jtiows(thread, ms != 0);
  if (JvmtiExport::should_post_monitor_wait()) {
    JvmtiExport::post_monitor_wait((JavaThread *)THREAD, (oop)obj(), ms);
  }
  ObjectSynchronizer::wait(obj, ms, CHECK);
JVM_END

重要的是,他要进入ObjectSynchronizer.wait方法

void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
    //UseBiasedLocking默认为true
  if (UseBiasedLocking) {
      //撤销对象头中包含的偏向锁
    BiasedLocking::revoke_and_rebias(obj, false, THREAD);
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
  }
  if (millis < 0) {
    TEVENT (wait - throw IAX) ;
    THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
  }
    //分配一个关联的ObjectMonitor实例
  ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
  DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
    //调用其wait方法
  monitor->wait(millis, true, THREAD);

  /* This dummy call is in place to get around dtrace bug 6254741.  Once
     that's fixed we can uncomment the following line and remove the call */
  // DTRACE_MONITOR_PROBE(waited, monitor, obj(), THREAD);
  dtrace_waited_probe(monitor, obj, THREAD);
}

这里就能看到了

//调用其wait方法
monitor->wait(millis, true, THREAD);

线程B在这个时候做了什么

在线程A调用wait方法的同时,线程B也已经启动了,就是jvm源码调试中的Thread-10

能看到这个时候线程B,已经进入了synchronized代码块,其中对应的指令就是monitorenter

 

对于monitorenter指令进行简单分析

先打印一下bt信息

#0  ObjectMonitor::enter (this=0x7fb1280036e0, __the_thread__=0x7fb11c001800) at /home/atzhang/atzhang/openjdksource/openjdk8/openjdk/hotspot/src/share/vm/runtime/objectMonitor.cpp:323
#1  0x00007fb14a8de3e6 in ObjectSynchronizer::slow_enter (obj=..., lock=0x7fb1345fc6a8, __the_thread__=0x7fb11c001800) at /home/atzhang/atzhang/openjdksource/openjdk8/openjdk/hotspot/src/share/vm/runtime/synchronizer.cpp:258
#2  0x00007fb14a8ddf70 in ObjectSynchronizer::fast_enter (obj=..., lock=0x7fb1345fc6a8, attempt_rebias=true, __the_thread__=0x7fb11c001800) at /home/atzhang/atzhang/openjdksource/openjdk8/openjdk/hotspot/src/share/vm/runtime/synchronizer.cpp:180
#3  0x00007fb14a538e2f in InterpreterRuntime::monitorenter (thread=0x7fb11c001800, elem=0x7fb1345fc6a8) at /home/atzhang/atzhang/openjdksource/openjdk8/openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp:573

如果看网上的一般文章那么分析的起点就是InterpreterRuntime::monitorenter ,其实是不对的,也就是99.8%的文章都是差了那么一乃乃,

在上下求索的过程中,发现连mashibing的公开课讲的课,黄俊将的synchronized底层实现都是错的,他错在了哪里了? 他用了openjdk1.8早期的

源码版本来讲偏向锁,其实这个源码还没有实现偏向锁,我也遇到了这个问题,发现怎么看也看不懂,那么真正的入口是

bytecodeInterpreter.cpp 

     /* monitorenter and monitorexit for locking/unlocking an object */

      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
          // code 3:如果锁对象的mark word的状态是偏向模式
          if (mark->has_bias_pattern()) {//has_bias_pattern las 3bit is 101 ?
            uintptr_t thread_ident;
            uintptr_t anticipated_bias_locking_value;
            thread_ident = (uintptr_t)istate->thread();
            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的epoch
            if  (anticipated_bias_locking_value == 0) {
              // already biased towards this thread, nothing to do
              if (PrintBiasedLockingStatistics) {
                (* BiasedLocking::biased_lock_entry_count_addr())++;
              }
              success = true;
            }
               /**
               *  初始化:偏向锁默认是延时初始化的,延迟的时间通过参数BiasedLockingStartupDelay控制,默认是4000ms默认是4000ms。
               *  初始化是在安全点下通过VMThread完成的,初始化时会把由SystemDictionary维护的所有已加载类的
               *  Klass的prototype_header修改成匿名偏向锁对象头,并把_biased_locking_enabled静态属性置为true,
               *  后续加载新的Klass时发现该属性为true,会将Klass的prototype_header修改成匿名偏向锁对象头。
               *  当创建某个Klass的oop时,会利用Klass的prototype_header来初始化该oop的对象头,即偏向锁初始化完成后,
               *  后续所有创建的oop的初始对象头都是匿名偏向锁的,在此之前创建的oop初始对象头都是无锁状态的。
               */
            else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
              // try revoke bias

              markOop header = lockee->klass()->prototype_header();
              if (hash != markOopDesc::no_hash) {
                header = header->copy_set_hash(hash);
              }
              //_biased_locking_enabled静态属性为false,默认是4000ms,尚未开启偏向锁,所以撤销偏向锁
              if (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) {
              // try rebias
              markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
              if (hash != markOopDesc::no_hash) {
                new_header = new_header->copy_set_hash(hash);
              }
              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 word
                // try to bias towards thread in case object is anonymously biased
              markOop 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 hint
              DEBUG_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;
            }
          }
          //if (mark->has_bias_pattern()) {//has_bias_pattern las 3bit is 101 ? } end of code
          //如果没符合的mark->has_bias_pattern就到这里了,
          // traditional lightweight locking
          // 如果偏向线程不是当前线程或没有开启偏向模式等原因都会导致success==false??存疑

          if (!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) {
              // Is it simple recursive case?
              // 判断是不是锁重入,进入这里说明obj是不是无锁状态,
              if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
                //code 10: 如果是锁重入,则直接将Displaced Mark Word设置为null
                  entry->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
        }
      }
      /**
       * 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 5,anticipated_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 Record(Lock Record的格式可以看第一篇文章)。设置mark word是无锁状态的原因是:
       轻量级锁解锁时是将对象头的mark word设置为Lock Record中的Displaced Mark Word,
       所以创建时设置为无锁状态,解锁时直接用CAS替换就好了。

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

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

这个比较复杂,如果你下载的版本是hotspot-87ee5ee27509那么你会看到源码为

     /* monitorenter and monitorexit for locking/unlocking an object */

      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);
          markOop displaced = lockee->mark()->set_unlocked();
          entry->lock()->set_displaced_header(displaced);
          if (Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
            // Is it simple recursive case?
            if (THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
              entry->lock()->set_displaced_header(NULL);
            } else {
              CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
            }
          }
          UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
        } else {
          istate->set_msg(more_monitors);
          UPDATE_PC_AND_RETURN(0); // Re-execute
        }
      }

 

这个真是个坑,那么分析一下这个过程

接着进入fast_enter

直接进入,

如果进入inflate方法,

会有其中情况:

  • // 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
  • ===以上是源码注解
  • 如果膨胀过了,锁已经是 ObjectMonitor,那么直接返回
  • 如果是Stack-locked,就是轻量级锁,那么锁膨胀
  • 如果膨胀中
  • 如果无锁Neutral,直接膨胀为重量级锁
  • 如果biased偏向锁,非法,因为上一个方法有判断,如果是偏向锁,直接返回不会到到达这里的

这里只贴了,锁此刻为重量级锁的时候,

那么返回的 ObjectMonitor * m 进入了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 ;
//原子的设置owner属性,如果_owner属性是NULL就将其设置为Self,否则返回当前的_owner属性
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  if (cur == NULL) {
      //设置成功,说明该Monitor没有被人占用
     // 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) {
      //设置失败,说明该Monitor就是当前线程占用的,此处进入enter是嵌套加锁情形
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions ++ ;
     return ;
  }

    //轻量级锁膨胀成重量级锁时,将owner设置为lock属性
  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 ;
  }

    //该Monitor被其他某个线程占用了,需要抢占
  // We've encountered genuine contention.
  assert (Self->_Stalled == 0, "invariant") ;

    //记录需要抢占的Monitor指针
  Self->_Stalled = intptr_t(this) ;
  //Knob_SpinEarly默认为1,即为true
    //TrySpin让当前线程自旋,自旋的次数默认可以自适应调整,如果进入安全点同步则退出自旋,返回1表示抢占成功
  // 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") ;

  //原子的将_count属性加1,表示增加了一个抢占该锁的线程
  // 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.
      //修改Java线程状态为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()
//会通过自旋,park等方式不断循环尝试获取锁,直到成功获取锁为止
      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);
  }

  Atomic::dec_ptr(&_count);
  assert (_count >= 0, "invariant") ;
  Self->_Stalled = 0 ;

  // Must either set _recursions = 0 or ASSERT _recursions == 0.
  assert (_recursions == 0     , "invariant") ;
  assert (_owner == Self       , "invariant") ;
  assert (_succ  != Self       , "invariant") ;
  assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;

  // The thread -- now the owner -- is back in vm mode.
  // Report the glorious news via TI,DTrace and jvmstat.
  // The probe effect is non-trivial.  All the reportage occurs
  // while we hold the monitor, increasing the length of the critical
  // section.  Amdahl's parallel speedup law comes vividly into play.
  //
  // Another option might be to aggregate the events (thread local or
  // per-monitor aggregation) and defer reporting until a more opportune
  // time -- such as next time some thread encounters contention but has
  // yet to acquire the lock.  While spinning that thread could
  // spinning we could increment JVMStat counters, etc.

  DTRACE_MONITOR_PROBE(contended__entered, this, object(), jt);
  if (JvmtiExport::should_post_monitor_contended_entered()) {
    JvmtiExport::post_monitor_contended_entered(jt, this);
  }

  if (event.should_commit()) {
    event.set_klass(((oop)this->object())->klass());
    event.set_previousOwner((TYPE_JAVALANGTHREAD)_previous_owner_tid);
    event.set_address((TYPE_ADDRESS)(uintptr_t)(this->object_addr()));
    event.commit();
  }

  if (ObjectMonitor::_sync_ContendedLockAttempts != NULL) {
     ObjectMonitor::_sync_ContendedLockAttempts->inc() ;
  }
}


// Caveat: TryLock() is not necessarily serializing if it returns failure.
// Callers must compensate as needed.

int ObjectMonitor::TryLock (Thread * Self) {
   for (;;) {
      void * own = _owner ;
      if (own != NULL) return 0 ;
      if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {
         // Either guarantee _recursions == 0 or set _recursions = 0.
         assert (_recursions == 0, "invariant") ;
         assert (_owner == Self, "invariant") ;
         // CONSIDER: set or assert that OwnerIsThread == 1
         return 1 ;
      }
      // The lock had been free momentarily, but we lost the race to the lock.
      // Interference -- the CAS failed.
      // We can either return -1 or retry.
      // Retry doesn't make as much sense because the lock was just acquired.
      if (true) return -1 ;
   }
}


标记了比较重要的方法,主要流程就是

  • cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ; cas,期望无人使用锁,(即 MonitorObject._owner为0x0),
  • 比如这个时候线程A 会在wati方法在释放锁之前这个_owner的值为ThreadA线程的一个包装对象,那么此时需要等待锁
  • 在ThreadA释放锁之后,这个时候如果进入执行这个方法那么,直接回返回,代表已经获取了锁
  • 锁进行等待的代码主要是EnterI ,主要实现如下
  •     for (;;) {
    
            if (TryLock (Self) > 0) break ;
            assert (_owner != Self, "invariant") ;
    
            if ((SyncFlags & 2) && _Responsible == NULL) {
               Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
            }
    
            // park self
            if (_Responsible == Self || (SyncFlags & 1)) {
                TEVENT (Inflated enter - park TIMED) ;
                Self->_ParkEvent->park ((jlong) RecheckInterval) ;
                // Increase the RecheckInterval, but clamp the value.
                RecheckInterval *= 8 ;
                if (RecheckInterval > 1000) RecheckInterval = 1000 ;
            } else {
                TEVENT (Inflated enter - park UNTIMED) ;
                Self->_ParkEvent->park() ;
            }
    
            if (TryLock(Self) > 0) break ;
    
            // The lock is still contested.
            // Keep a tally of the # of futile wakeups.
            // Note that the counter is not protected by a lock or updated by atomics.
            // That is by design - we trade "lossy" counters which are exposed to
            // races during updates for a lower probe effect.
            TEVENT (Inflated enter - Futile wakeup) ;
            if (ObjectMonitor::_sync_FutileWakeups != NULL) {
               ObjectMonitor::_sync_FutileWakeups->inc() ;
            }
            ++ nWakeups ;
    
            // Assuming this is not a spurious wakeup we'll normally find _succ == Self.
            // We can defer clearing _succ until after the spin completes
            // TrySpin() must tolerate being called with _succ == Self.
            // Try yet another round of adaptive spinning.
            if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
    
            // We can find that we were unpark()ed and redesignated _succ while
            // we were spinning.  That's harmless.  If we iterate and call park(),
            // park() will consume the event and return immediately and we'll
            // just spin again.  This pattern can repeat, leaving _succ to simply
            // spin on a CPU.  Enable Knob_ResetEvent to clear pending unparks().
            // Alternately, we can sample fired() here, and if set, forgo spinning
            // in the next iteration.
    
            if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {
               Self->_ParkEvent->reset() ;
               OrderAccess::fence() ;
            }
            if (_succ == Self) _succ = NULL ;
    
            // Invariant: after clearing _succ a thread *must* retry _owner before parking.
            OrderAccess::fence() ;
        }

    上边的方法里面可以主要看出

    • TryLock(Self),试着cas一把,如果没人占用锁就,占用锁
    • Self->_ParkEvent->park ((jlong) RecheckInterval) ; 线程挂起一段时间
    • 醒了之后,接着TryLock(Self),若不成功则一直循环,

接着看线程A在wait()的时候做了什么

此时线程B在一直循环等待获取锁,线程A开始执行wait方法,2者是并行执行的

 

可以看到其实Thread9 就是线程A在jvm中对应的线程

那么看到在threadA执行完wait方法之后,先释放了锁.在把自己所对应的linux线程挂起

那么还查threadB.notify分析

流程:

lock.notify()   //调用lock对象的方法

-->                 //这个obj就是这个lock,将lock作为参数

  ObjectSynchronizer::notify(obj, CHECK); 

-->                 //找到obj对象上的重量锁ObjectMonitor 

ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
-->                 // 调用锁上的notify方法

void ObjectMonitor::notify(TRAPS) 

 

那么现在进入关键步骤

 

进入的去DequeueWaiter()

接着

 打印

(gdb) p _WaitSet
$1 = (ObjectWaiter * volatile) 0x7f88bbbfa110

(gdb) p *node 
$6 = (ObjectWaiter) {
  <StackObj> = {
    <AllocatedObj> = {
      _vptr.AllocatedObj = 0x7f88d66797f0 <vtable for ObjectWaiter+16>
    }, <No data fields>}, 
  members of ObjectWaiter: 
  _next = 0x7f88bbbfa110, 
  _prev = 0x7f88bbbfa110, 
  _thread = 0x7f88d0160800, 
  _notifier_tid = 140225242177872, 
  _event = 0x7f88d0161b00, 
  _notified = 0, 
  TState = ObjectWaiter::TS_WAIT, 
  _Sorted = (unknown: 3019902016), 
  _active = false
}

看这个链表节点的next和priev都是自己

  

ObjectWaiter* next = node->_next;   找到下一个节点node3
   if (next == node) {  //如果next指向自己说明,只有一个node节点,那么久就将_WaitSet置空
  assert(node->_prev == node, "invariant check");
  _WaitSet = NULL;
 } else {    //不进入这里,这里是中间节点的情况
  ObjectWaiter* prev = node->_prev;  找到上一个节点 ,命名node1

  next->_prev = prev;    令下一个节点的上一个指向 node1
  prev->_next = next;     令node1的_next指针指向node3
  if (_WaitSet == node) {    
  _WaitSet = next;
    }
  }
  node->_next = NULL; 接着道这里,将node节点的next ,priev置空
  node->_prev = NULL;

这就

那么这样就取出来了objectWaiter 对象

接下来对ObjectWaiter对象的处理方式,根据Policy的不同而不同:
Policy == 0:放入_EntryList队列的排头位置;
Policy == 1:放入_EntryList队列的末尾位置;
Policy == 2:_EntryList队列为空就放入_EntryList,否则放入_cxq队列的排头位置;

那么接着进入ThreadB的monitorexit指令

 

小结一下,线程B释放了锁之后,执行的操作如下:

偏向锁逻辑,此处未命中;
根据QMode的不同,将ObjectWaiter从_cxq或者_EntryList中取出后唤醒;
唤醒的元素会继续执行挂起前的代码,按照我们之前的分析,线程唤醒后,就会通过CAS去竞争锁,此时由于线程B已经释放了锁,那么此时应该能竞争成功;

唤醒目标线程,就是A之前wait方法里面挂起了,那么接着看wait方法的实现

还有

//ReenterI和EnterI的逻辑基本相同,用于获取对象锁
void ATTR ObjectMonitor::ReenterI (Thread * Self, ObjectWaiter * SelfNode) {
    assert (Self != NULL                , "invariant") ;
    assert (SelfNode != NULL            , "invariant") ;
    assert (SelfNode->_thread == Self   , "invariant") ;
    assert (_waiters > 0                , "invariant") ;
    //校验目标对象的对象头就是当前ObjectMonitor的指针
    assert (((oop)(object()))->mark() == markOopDesc::encode(this) , "invariant") ;
    assert (((JavaThread *)Self)->thread_state() != _thread_blocked, "invariant") ;
    JavaThread * jt = (JavaThread *) Self ;
 
    int nWakeups = 0 ;
    for (;;) {
        ObjectWaiter::TStates v = SelfNode->TState ;
        //校验状态
        guarantee (v == ObjectWaiter::TS_ENTER || v == ObjectWaiter::TS_CXQ, "invariant") ;
        assert    (_owner != Self, "invariant") ;
        
        //尝试获取锁
        if (TryLock (Self) > 0) break ;
        //尝试自旋获取锁
        if (TrySpin (Self) > 0) break ;
 
        TEVENT (Wait Reentry - parking) ;
 
        {   
           //修改线程状态
           OSThreadContendState osts(Self->osthread());
           ThreadBlockInVM tbivm(jt);
 
           jt->set_suspend_equivalent();
           //SyncFlags默认是0
           if (SyncFlags & 1) {
              Self->_ParkEvent->park ((jlong)1000) ;
           } else {
              Self->_ParkEvent->park () ;
           }
 
           // were we externally suspended while we were waiting?
           for (;;) {
              //ExitSuspendEquivalent默认返回false
              if (!ExitSuspendEquivalent (jt)) break ;
              if (_succ == Self) { _succ = NULL; OrderAccess::fence(); }
              jt->java_suspend_self();
              jt->set_suspend_equivalent();
           }
        }
 
        //尝试获取锁
        if (TryLock(Self) > 0) break ;
 
        TEVENT (Wait Reentry - futile wakeup) ;
        ++ nWakeups ;
 
        // Assuming this is not a spurious wakeup we'll normally
        // find that _succ == Self.
        if (_succ == Self) _succ = NULL ;
 
        // Invariant: after clearing _succ a contending thread
        // *must* retry  _owner before parking.
        OrderAccess::fence() ;
 
        if (ObjectMonitor::_sync_FutileWakeups != NULL) {
          ObjectMonitor::_sync_FutileWakeups->inc() ;
        }
    }//for循环结束
 
    //for循环结束,已经获取了锁
    assert (_owner == Self, "invariant") ;
    assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
    //从链表中移除
    UnlinkAfterAcquire (Self, SelfNode) ;
    if (_succ == Self) _succ = NULL ;
    assert (_succ != Self, "invariant") ;
    //修改状态为TS_RUN
    SelfNode->TState = ObjectWaiter::TS_RUN ;
    OrderAccess::fence() ;      // see comments at the end of EnterI()
}

本文结束

 

posted @ 2021-06-09 12:04  张艳涛&java  阅读(636)  评论(0编辑  收藏  举报