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

线程的notify方法_forkjoinpool默认线程数量从源码角度剖析notify/notifyAll方法到底做了些什么,线程是如何唤醒的。

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

众所周知,使用notify/notifyAll方法能唤醒wait等待的线程,那么在底层源码究竟做了些什么呢?

本章内容要解决的问题

问题1:notify/nofityAll真的唤醒了线程吗?

问题2:notify/nofityAll底层逻辑是怎样的?

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

(图1-1)

带着这两个问题来具体探究一下:

在(图1-1)中,java中的notify/nofityAll方法对应c++源码jvm.cpp中的JVM_MonitorNotifyJVM_MonitorNotifyAll方法。

1.首先,进入jvm.cpp文件,查看JVM_MonitorNotify方法。

在 JVM_MonitorNotify方法中,调用了返回值为ObjectSynchronizer的notify方法(图1-2);

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

 

(图1-2)

2.再次进入方法,可以看到最终又调用了一个notify方法,继续跟进

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

 (图1-3)

 好了,在这里是真正的核心逻辑了  (图1-4):

首先有一个policy策略(默认为2),接着DequeueWaiter,从WaitSet取出ObjectWaiter节点(waitSet等待队列是一个双向循环链表,调用object.wait,会把线程包装为一个ObjectWaiter节点,然后方入这个链表中)

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

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

  (图1-4)

 来看看DequeueWaiter的逻辑(从双向链表中取出节点)(图1-5)

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

(图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参数不可修改(除非直接更改源码打包)

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

(图1-6)

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

(图1-7)

至此,notify方法结束。

也就是说没有任何有关唤醒的操作。那么第一个问题答案出来了:notify/nofityAll真的唤醒了线程吗?答案是并没有。

那么什么时候唤醒线程呢?稍作回答。

先来看看notifyAll方法源码吧:

1.notifyAll调用了返回值为ObjectSynchronizer的notifyall方法(图1-2);

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

 进入看看,也是跟着调用notifyall方法,继续进入

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

好了,核心来了… (图1-8)

可以看到notifyall源码,和notify源码几乎是一样的,唯一就是多了个for死循环;

也就是说,notifyall方法其实是循环去执行notify逻辑(从waitset链表中取出节点,然后根据策略挪动节点,直至全部取出),仅此而已

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

 (图1-8)

至此,notifyall方法结束。

=========================================================================

看完了notify/notifyall源码逻辑,其实并没有任何唤醒操作,有的仅仅是挪动节点而已;回到中间提出的问题,那么什么时候唤醒线程呢?

其实是在synchronized代码块退出后,释放锁时根据QMode策略进行唤醒的(图1-9、1-10、1-11)

也就是说在monitorexit方法中的exit方法里(部分代码)

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

(图1-9)

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

(图1-10)

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

 (图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方法_forkjoinpool默认线程数量

总结:

线程的notify/nofityAll方法在jvm源码中并没有唤醒线程,而是从waitSet链表取出一个节点进行挪动(根据policy策略,默认为2,判断entrylist是否为空,为空就放入到entrylist中,否则放入到cxq队列的排头位置),等到真正出了synchronized代码块时,根据QMode策略(默认为0,啥也不做,向下继续执行;entrylist是否为空,不为空取出一个唤醒;为空,从cxq集合取出一个唤醒)​挪动节点然后唤醒

最后,附上整体示意图:

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

 notify与policy挪动策略图

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

         QMode策略唤醒示意图

最后的问题?

1. waitSet、entryList、cxq是什么?有什么作用?三大队列?

简单解释一下:

多线程的各个方法包括synchronized的实现,与三大队列息息相关。

waitSet是线程等待集合,是一个双向循环链表,调用wait方法的线程将会在里面。

entrylist是线程争抢失败的集合,是一个双向链表。

cxq多线程竞争锁是进入的集合,是一个栈结构。

线程节点在多线程环境下操作时,在三个集合中不断地转换,但同一时间只能在某一个集合中,不能多个集合同时存在。

2.线程的其他方法?

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

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 对象转json忽略空参「建议收藏」

    对象转json忽略空参「建议收藏」有时候我们在传json的时候需要过滤掉那些数据为空的参数,我们可以这样:JsonUtil.object2JSON(request,SerializerFeature.WriteDateUseDateFormat); 当然,也可以这样:JSON.toJSONString(request)这两种方式都需要添加阿里的jar包<dependency> <gr…

    2025年12月5日
    4
  • linux下卸载jdk1.7安装jdk1.8「建议收藏」

    linux下卸载jdk1.7安装jdk1.8「建议收藏」卸载jdk1.7查询java安装版本java-version查询java的安装目录echo$JAVA_HOME我以前在/etc/profile里配置了全局的JAVA_HOME、CLASSPATH和PATH,针对我这种情况想卸载的话,必须把下边这些语句删除vi/etc/profile/i进入编辑,删除之后:wq保存退出.这样的话,你的jdk1.7实际…

    2022年9月25日
    4
  • 分散式-ubuntu12.04安装hadoop1.2.1

    分散式-ubuntu12.04安装hadoop1.2.1

    2022年1月5日
    51
  • 【OpenCV人脸识别入门教程之二】人脸检测

    【OpenCV人脸识别入门教程之二】人脸检测本篇文章主要介绍了如何使用OpenCV实现人脸检测的功能。要实现人脸识别功能,首先要进行人脸检测,判断出图片中人脸的位置,才能进行下一步的操作。人脸检测的方法介绍OpenCV中的方法函数参数含义代码实现

    2022年6月7日
    86
  • BigDecimal.setScale()方法实用技巧

    BigDecimal.setScale()方法实用技巧方法使用scale()方法用于格式化小数点setScale(1,BigDecimal.ROUND_DOWN)直接删除多余的小数位,如2.35会变成2.3setScale(1,BigDecimal.ROUND_UP)进位处理(无论小数如何),2.35变成2.4setScale(1,BigDecimal.ROUND_HALF_UP)四舍五入,2.35变成2.4(目前最常用的金额处理方法…

    2022年10月20日
    4
  • 静态路由与默认路由的配置_静态路由和默认路由哪个快

    静态路由与默认路由的配置_静态路由和默认路由哪个快原理简述:1。静态路由:是指用户或网络管理员手工配置的路由信息。当网络拓扑结构或链路状态发生改变时,需要网络管理员手工配置静态路由信息。相比较动态路由协议,静态路由无需频繁的交换各自的路由表,配置简单,比较适合小型、简单的网络环境。不适合大型和复杂的网络环境的原因是:当网络拓扑结构和链路状态发生改变时,网络管理员需要做大量的调整,工作量繁重,而且无法感知错误发生,不易排错。2。默认路由…

    2022年9月25日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号