线程的notify方法_forkjoinpool默认线程数量

2022-10-04 21:30:17 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

众所周知,使用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.线程的其他方法?

这些问题将在后续文章中解答…感谢各位的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/195970.html原文链接:https://javaforall.cn

0 人点赞