java线程池ThreadPoolExecutor实现原理详解

 更新时间:2023年12月16日 10:57:29   作者:那个天真的人  
这篇文章主要介绍了java线程池ThreadPoolExecutor实现原理详解,ThreadPoolExecutor是线程池实现类,会动态创建多个线程,并发执行提交的多个任务,需要的朋友可以参考下

前言

做java开发的,一般都避免不了要面对java线程池技术,像tomcat之类的容器天然就支持多线程。

即使是做偏后端技术,如处理一些消息,执行一些计算任务,也经常需要用到线程池技术。

鉴于线程池技术的重要性,接下来会分多篇介绍java中提供的ThreadPoolExecutor线程池实现的底层机制。

只有对机制了然于胸,才能更好驾驭这把利器。

线程池技术演示流程

  • 关键概念: 如果说怎么最容易了解线程池的实现原理,那就是一步一步动态的演示。为了便于理解,这里先介绍几个线程池用到的概念。
  • ThreadPoolExecutor: 这是线程池实现类,会动态创建多个线程,并发执行提交的多个任务;
  • Worker: 是个Runnable实现类来的,内部会创建一个线程,一直循环不断执行任务,所以可以认为一个Worker就是一个工作线程;
  • corePoolSize: 当池中总线程数<corePoolSize时,提交一个任务创建一个新线程,而不管已经存在的线程是不是闲着,通常情况下,一旦线程数达 到了corePoolSize,那么池中总线程数是不会跌破到corePoolSize以下的。(除非 allowCoreThreadTimeOut=true并且keepAliveTime>0);
  • maximumPoolSize: 当池中线程数达到了corePoolSize,这时候新提交的任务就会放入等待队列中,一般情况下,这些任务会被前面创建的 corePoolSize个线程执行。当任务提交速度过快,队列满了,这时候,如果当前总线程数<maximumPoolSize,那么线程池会创建一个新的线程来执行新提交的任务,否则根据策略放弃任务;
  • keepAliveTime:存活时间,分两种情况: (1)allowCoreThreadTimeOut=true,所有线程,一旦创建后,在keepAliveTime时间内,如果没有任务可以执行,则该线程会退出并销毁,这样的好处是系统不忙时可以回收线程资源;(2)allowCoreThreadTimeOut=false,如果总线程数<=corePoolSize,那么这些线程是不会退出的,他们会一直不断的等待任务并执行,哪怕当前没有任务,但如果线程数>corePoolSize,而且一旦一个线程闲的时间超过 keepAliveTime则会退出,但一旦降低到corePoolSize,则不会再退出了。
  • allowCoreThreadTimeOut: 用于决定是否在系统闲时可以逐步回收所有的线程,如果为allowCoreThreadTimeOut=true,必须结合keepAliveTime一起使用,用于决定当线程数<corePoolSize时,是否要回收这些线程。
  • workQueue:这是一个阻塞队列,当线程数>=corePoolSize,这时候提交的任务将会放入阻塞队列中,如果阻塞队列是无界的,那么总的线程数是不可能>corePoolSize的,即maximumPoolSize属性就是无用的;如果阻塞队列是有界的,而且未满,则任务入队,否则根据maximumPoolSize的值判断是要新建线程执行新任务或者是根据策略丢弃任务。

有了以上的概念,接下来将根据 allowCoreThreadTimeOut的值分两种场景进行演示说明。

演示一: allowCoreThreadTimeOut=true

1、 初值设定: corePoolSize=2; maximumPoolSize=3;keepAliveTime=10s; workQueue容量为2; 初始状态图如下所示:

这里写图片描述

2、submit一个任务A,由于总线程数0<corePoolSize; 此时会创建一个线程执行任务A,状态图如下:

这里写图片描述

3、submit一个任务B,由于总线程数1<corePoolSize,此时会创建一个线程执行任务B,状态图如下:

这里写图片描述

4、submit一个任务C,由于总线程数 2=corePoolSize,workQueue不满,这时候任务C入队列,状态图如下:

这里写图片描述

5、submit一个任务D,很明显,任务D入队列,状态图如下:

这里写图片描述

6、submit一个任务E,这时候,线程数2=corePoolSize,workQueue也已经满了,判断发现线程数2<maximumPoolSize,所以继续创建线程执行任务E,状态图如下:

这里写图片描述

7、submit一个任务F,这时候,2=corePoolSize,workQueue已满,判断发现线程数3=maximumPoolSize,这种情况下,线程池会根据策略来决定是否要放弃当前任务,或者是把workQueue中一个任务删除,然后入队新的任务,也可以自定义策略,比如,持久化到DB之类的,或者是发出警报。我们假设是直接丢弃策略,这时候状态图不变。

8、这会没有新任务到来了,各个任务陆续执行完了,包括队列中的C和D也执行完了,这时候,由于当前场景为allowCoreThreadTimeOut=true,如果在等待keepAliveTime时间后线程仍旧无法获取新的任务,线程将会自行退出,这将导致最终所有线程都退出了,也就是又再次回到了原始状态,如下图所示:

这里写图片描述

说得更简单一些就是:在 allowCoreThreadTimeOut=true时,如果一个线程等了keepAliveTime还无法获取新任务,则退出。

演示二:allowCoreThreadTimeOut=false

1~7 的步骤与状态跟“演示一”是一样的,所以这里不再赘述,这时候状态图如下:

这里写图片描述

8、这会没有新任务到来了,各个任务陆续执行完了,包括队列中的C和D也执行完了,这时候,由于当前场景为allowCoreThreadTimeOut=false,并且线程数3>corePoolSize,这时候每个线程都感知到线程数过多,所以它们都会尝试把自己停止掉,实现中最终只会停止一个线程,剩余线程数2=corePoolSize,接下来,哪怕一直没有新任务来,这corePoolSize个线程也不会退出,一直存活着等待接收任务。这时候,状态图如下:

这里写图片描述

线程池的状态

前一部分演示了线程池的基本实现原理,这一小节介绍一下线程池的状态,线程池概括上讲有5种状态,如下图所示:

这里写图片描述

  • RUNNING状态: 创建线程池的时候,线程池的初始状态为 RUNNING,接着就可以提交任务执行了。
  • SHUTDOWN状态: 当在RUNNING状态调用shutdown()时,线程池状态会被改为SHUTDOWN,这时候,submit任务的时候,会被拒绝,可以使用多种拒绝策略, 比如最简单就是直接丢弃任务。至于正在执行中的线程,会继续执行,同时会把阻塞队列中的任务也一并执行完毕,等到全部任务执行完毕,线程池会进入 TIDYING状态,等执行钩子方法terminated()之后,就会进入最终状态TERMINATED,这时候,整个线程池完全终止。
  • STOP状态: 当在RUNNING状态调用shutdownNow()时,线程池状态会被改为STOP,这时候,submit任务会被拒绝,那么如果有任务执行到一半,该怎么处理?其实,执行shutdownNow()时,会中断各个工作线程,所以任务会如何执行要看任务做的是什么事情,有没有处理中断异常。而阻塞队列如何有任务,这些任务将不会再执行,shutdownNow()执行后,将会返回阻塞队列中的未执行的任务列表。
  • TIDYING状态: TIDYING只是一个过渡状态,当所有工作线程都停止后,线程池的状态会进入TIDYING,然后执行一个钩子方法terminated(),最后线程池会进入TERMINATED状态。
  • TERMINATED状态: 线程池终止状态,这个没什么可说的了,大家都明白。

总结

理解线程池最主要是要理解线程池几个主要的配置参数,如果不看实现细节,原理还是比较简单的。在了解原理的基础上再去看代码,就会事半功倍。

到此这篇关于java线程池ThreadPoolExecutor实现原理详解的文章就介绍到这了,更多相关ThreadPoolExecutor实现原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决Sentinel链路模式规则无效问题

    解决Sentinel链路模式规则无效问题

    本文介绍了如何在Spring Cloud Alibaba项目中使用Sentinel链路流控规则,并解决规则不生效的问题,通过关闭Sentinel过滤器,可以避免重复统计请求
    2025-01-01
  • java实现即赋值也判断的写法示例

    java实现即赋值也判断的写法示例

    这篇文章主要为大家介绍了java实现即赋值也判断的写法示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • 详解Java8新特性之interface中的static方法和default方法

    详解Java8新特性之interface中的static方法和default方法

    这篇文章主要介绍了Java8新特性之interface中的static方法和default方法,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-08-08
  • java贪心算法初学感悟图解及示例分享

    java贪心算法初学感悟图解及示例分享

    这篇文章主要为大家介绍了本人在初学java贪心算法的感悟,并通过图解及示例代码的方式分享给大家,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-11-11
  • Java Map的几种循环方式总结

    Java Map的几种循环方式总结

    这篇文章主要是对Java中Map的几种循环方式进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2013-12-12
  • Java如何接收前端easyui datagrid传递的数组参数

    Java如何接收前端easyui datagrid传递的数组参数

    这篇文章分享一下怎么在easyui的datagrid刷新表格时,在后端java代码中接收datagrid传递的数组参数,本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2023-11-11
  • Spring Boot Admin实践详解

    Spring Boot Admin实践详解

    在本篇文章里小编给大家整理了关于Spring Boot Admin实践的相关知识点,有需要的朋友们可以学习下。
    2019-12-12
  • Java基本数据类型与对应的包装类(动力节点java学院整理)

    Java基本数据类型与对应的包装类(动力节点java学院整理)

    Java是面向对象的编程语言,包装类的出现更好的体现这一思想,Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。 下面通过本文给大家详细介绍,感兴趣的朋友一起学习吧
    2017-04-04
  • SpringBoot框架DataSource多数据源配置方式

    SpringBoot框架DataSource多数据源配置方式

    这篇文章主要介绍了SpringBoot框架DataSource多数据源配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • Java中的ByteArrayInputStream详解

    Java中的ByteArrayInputStream详解

    Java中,ByteArrayInputStream类是实现内存级别的字节流读取的工具,可以从字节数组中读取数据,这个类位于java.io包中,继承自InputStream,ByteArrayInputStream的主要特点有:在内存中操作,不涉及磁盘IO,可以重用流读取数据
    2024-09-09

最新评论