Java的wait和notify学习三部曲之一:JVM源码分析

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,代码功能简述如下:

  1. 启动线程A,取得锁之后先启动线程B再执行wait()方法,释放锁并等待;

  2. 线程B启动之后会等待锁,A线程执行wait()之后,线程B取得锁,然后启动线程C,再执行notify唤醒线程A,最后退出synchronize代码块,释放锁;

  3. 线程C启动之后就一直在等待锁,这时候线程B还没有退出synchronize代码块,锁还在线程B手里;

  4. 线程A在线程B执行notify()之后就一直在等待锁,这时候线程B还没有退出synchronize代码块,锁还在线程B手里;

  5. 线程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源码可以聚焦主线,不要被一些支线的次要的代码卡住(例如一些异常处理,监控和上报等):

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

  2. 线程C启动后,由于此时线程B持有锁,那么线程C此时在干啥?

  3. 线程B在notify()的时候做了什么?

  4. 线程B释放锁的时候做了什么?

源码中最重要的注释信息

在源码中有段注释堪称是整篇文章最重要的说明,请大家始终记住这段信息,处处都用得上:

ObjectWaiter对象存在于WaitSet、EntryList、cxq等集合中,或者正在这些集合中移动

原文如下:

640?wx_fmt=png

请务必记住这三个集合:WaitSet、EntryList、cxq

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

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

打开hotspot/src/share/vm/runtime/objectMonitor.cpp,看ObjectMonitor::wait方法:

640?wx_fmt=png

如上图所示,有两处代码值得我们注意:

  1. 绿框中将当前线程包装成ObjectWaiter对象,并且状态为TS_WAIT,这里对应的是jstack看到的线程状态WAITING;

  2. 红框中调用了AddWaiter方法,跟进去看下:

640?wx_fmt=png

这个ObjectWaiter对象被放入了WaitSet中,WaitSet是个环形双向链表(circular doubly linked list)

回到ObjectMonitor::wait方法接着往下看,会发现关键代码如下图,当前线程通过park()方法开始挂起(suspend):

640?wx_fmt=png

至此,我们把wait()方法要做的事情就理清了:

  1. 包装成ObjectWaiter对象,状态为TS_WAIT;

  2. ObjectWaiter对象被放入_WaitSet中;

  3. 当前线程挂起;

线程B持有锁的时候线程C在干啥

此时的线程C无法进入synchronized{}代码块,用jstack看应该是BLOCKED状态,如下图:

640?wx_fmt=png

我们看看monitorenter指令对应的源码吧,位置:openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))

#ifdef ASSERT

thread->last_frame().interpreter_frame_verify_monitor(elem);

#endif

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”);

if (UseBiasedLocking) {

// Retry fast entry if bias is revoked to avoid unnecessary inflation

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

上面的代码有个if (UseBiasedLocking)判断,是判断是否使用偏向锁的,本例中的锁显然已经不属于当前线程C了,所以我们还是直接看slowenter(hobj, elem->lock(), CHECK)方法吧;

打开openjdk/hotspot/src/share/vm/runtime/synchronizer.cpp:

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()) {

// Anticipate successful CAS – the ST of the displaced mark must

// be visible <= the ST performed by the CAS.

lock->set_displaced_header(mark);

//无锁状态就去竞争锁

if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {

TEVENT (slow_enter: release stacklock) ;

return ;

}

// Fall through to inflate() …

} else

if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {

//如果处于有锁状态,就检查是不是当前线程持有锁,如果是当前线程持有的,就return,然后就能执行同步代码块中的代码了

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

// 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);

}

线程C在上面代码中的执行顺序如下:

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-H4vzn99F-1710826840541)]
[外链图片转存中…(img-WvoCcL4J-1710826840542)]
[外链图片转存中…(img-UyVoYcIs-1710826840542)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-Kw1bRPXN-1710826840542)]

  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值