众所周知,使用notify/notifyAll方法能唤醒wait等待的线程,那么在底层源码究竟做了些什么呢?
本章内容要解决的问题
问题1:notify/nofityAll真的唤醒了线程吗?
问题2:notify/nofityAll底层逻辑是怎样的?
(图1-1)
带着这两个问题来具体探究一下:
在(图1-1)中,java中的notify/nofityAll方法对应c++源码jvm.cpp中的JVM_MonitorNotify和JVM_MonitorNotifyAll方法。
1.首先,进入jvm.cpp文件,查看JVM_MonitorNotify方法。
在 JVM_MonitorNotify方法中,调用了返回值为ObjectSynchronizer的notify方法(图1-2);
(图1-2)
2.再次进入方法,可以看到最终又调用了一个notify方法,继续跟进
(图1-3)
好了,在这里是真正的核心逻辑了 (图1-4):
首先有一个policy策略(默认为2),接着DequeueWaiter,从WaitSet取出ObjectWaiter节点(waitSet等待队列是一个双向循环链表,调用object.wait,会把线程包装为一个ObjectWaiter节点,然后方入这个链表中)
(图1-4)
来看看DequeueWaiter的逻辑(从双向链表中取出节点)(图1-5)
(图1-5)
重点来了...(图1-6/1-7)
根据policy策略挪动ObjectWaiter节点
根据源码可以看到,
Policy策略:
Policy=0:将ObjectWaiter放入到enteylist队列的排头位置
Policy=1:放入到entrylist队列末尾位置
Policy=2:判断entrylist是否为空,为空就放入到entrylist中,否则放入到cxq队列的排头位置(默认)
Policy=3:判断cxq是否为空,如果为空,直接放入头部,否则放入cxq队列末尾位置
其余情况:直接唤醒线程(unpark) 但这几乎是不可能的,因为jdk默认策略为2 且jvm参数不可修改(除非直接更改源码打包)
(图1-6)
(图1-7)
至此,notify方法结束。
也就是说没有任何有关唤醒的操作。那么第一个问题答案出来了:notify/nofityAll真的唤醒了线程吗?答案是并没有。
那么什么时候唤醒线程呢?稍作回答。
先来看看notifyAll方法源码吧:
1.notifyAll调用了返回值为ObjectSynchronizer的notifyall方法(图1-2);
进入看看,也是跟着调用notifyall方法,继续进入
好了,核心来了... (图1-8)
可以看到notifyall源码,和notify源码几乎是一样的,唯一就是多了个for死循环;
也就是说,notifyall方法其实是循环去执行notify逻辑(从waitset链表中取出节点,然后根据策略挪动节点,直至全部取出),仅此而已
(图1-8)
至此,notifyall方法结束。
=========================================================================
看完了notify/notifyall源码逻辑,其实并没有任何唤醒操作,有的仅仅是挪动节点而已;回到中间提出的问题,那么什么时候唤醒线程呢?
其实是在synchronized代码块退出后,释放锁时根据QMode策略进行唤醒的(图1-9、1-10、1-11)
也就是说在monitorexit方法中的exit方法里(部分代码):
(图1-9)
(图1-10)
(图1-11)
根据不同的QMode策略挪动线程并唤醒线程
再来看看具体的挪动唤醒策略:
根据QMode策略唤醒:
QMode=2,取cxq头部节点直接唤醒
QMode=3,如果cxq非空,把cxq队列放置到entrylist的尾部(顺序跟cxq一致)
QMode=4,如果cxq非空,把cxq队列放置到entrylist的头部(顺序跟cxq相反)
QMode=0,啥都不做,继续往下走(QMode默认是0)默认是0
Qmode=0的判断逻辑就是先判断entrylist是否为空,如果不为空,则取出第一个唤醒,如
果为空再从cxq里面获取第一个唤醒
最后看看唤醒的方法ExitEpilog:
总结:
线程的notify/nofityAll方法在jvm源码中并没有唤醒线程,而是从waitSet链表取出一个节点进行挪动(根据policy策略,默认为2,判断entrylist是否为空,为空就放入到entrylist中,否则放入到cxq队列的排头位置),等到真正出了synchronized代码块时,根据QMode策略(默认为0,啥也不做,向下继续执行;entrylist是否为空,不为空取出一个唤醒;为空,从cxq集合取出一个唤醒)挪动节点然后唤醒。
最后,附上整体示意图:
notify与policy挪动策略图
QMode策略唤醒示意图
最后的问题?
1. waitSet、entryList、cxq是什么?有什么作用?三大队列?
简单解释一下:
多线程的各个方法包括synchronized的实现,与三大队列息息相关。
waitSet是线程等待集合,是一个双向循环链表,调用wait方法的线程将会在里面。
entrylist是线程争抢失败的集合,是一个双向链表。
cxq多线程竞争锁是进入的集合,是一个栈结构。
线程节点在多线程环境下操作时,在三个集合中不断地转换,但同一时间只能在某一个集合中,不能多个集合同时存在。
2.线程的其他方法?
这些问题将在后续文章中解答...感谢各位的阅读。