首页 > 编程语言 >Java 线程池之ThreadPoolExecutor学习总结

Java 线程池之ThreadPoolExecutor学习总结

时间:2022-11-06 18:38:06浏览次数:46  
标签:Java 队列 创建 maximumPoolSize corePoolSize 任务 线程 ThreadPoolExecutor

前提

java version "1.8.0_25"

池简述

软件开发活动中,我们经常会听到数据库连接池、内存池、线程池等各种“池”概念,这些“池”到底是什么东西呢?程序的世界里,我们可以将池简单的理解为一种容器类数据结构,比如列表。程序处理信息的过程中,可能会依赖某些资源或者对象(暂且统一称之为对象),比如数据库连接,来执行一些高频操作,比如数据表查询,此时,如果被依赖对象的存活时间比较短,那就意味着需要频繁的创建和销毁对象,这可能会很耗时、耗系统资源(CPU、内存、磁盘、网络等)。为了解决这个问题,进行程序设计时,可能会考虑在程序初始化时,预先创建一批所需对象,并存储到池中,或者根据需要即时创建对象,并在使用完成后,将对象添加到池中,这样,当程序需要(再次)使用对象时,可以直接从池中直接获取现有的对象,节省了频繁创建和销毁对象带来的资源浪费,这就是池的作用,为程序提供复用对象或者提前分配资源的能力。

ThreadPoolExecutor线程池介绍

下文仅针对线程池的一些要点做介绍

任务处理流程

核心线程池大小(​​corePoolSize​​​)和最大线程池大小(​​maximumPoolSize​​)

​ThreadPoolExecutor​​​会根据​​corePoolSize​​​(保持存活(不允许超时退出等)的最小工作线程数,如果设置了​​allowCoreThreadTimeOut​​​为​​true​​​,则该值为0。可通过​​getPoolSize​​​方法获取该值) 和​​maximumPoolSize​​​(线程池中允许的最大线程数,可通过​​getMaximumPoolSize​​获取该值)设置的界限自动调整线程池的大小。

当通过​​execute(Runnable)​​​ 方法提交新任务后,如果正在运行的线程的数量小于​​corePoolSize​​​,则创建新线程来处理请求,即使存在其它空闲的工作线程,否则如果正在运行的线程的数量大于​​corePoolSize​​​,但小于​​maximumPoolSize​​​,则仅仅在队列已经满时才会创建新线程来处理请求。设置​​corePoolSize​​​等于​​maximumPoolSize​​则表示创建一个固定大小的线程池。

通过设置​​maximumPoolSize​​​为基本无界的值,例如​​Integer.MAX_VALUE​​​,则允许线程池容纳任意并发任务数。大多数情况下,​​corePoolSize​​​和​​maximumPoolSize​​​仅在构建时设置,但也可以分别用使用​​setCorePoolSize​​​和​​setMaximumPoolSize​​对其进行动态更改。

按需创建线程

默认情况下,仅在新任务到达时创建和启动线程,即便是核心线程。可以使用​​prestartCoreThread​​​或者​​prestartAllCoreThreads​​对此进行动态更改。如果使用非空队列构造线程池,你可能会想预先启动线程。

创建新线程

使用​​ThreadFactory​​​创建新线程。如果未指定,则使用​​Executors.defaultThreadFactory​​​,其创建的线程都位于相同线程组,且拥有相同的优先级​​NORM_PRIORITY​​​以及非守护状态。通过提供不同的线程工程​​ThreadFactory​​​,可以修改线程的名称,线程组,优先级,守护状态等等。当​​newThread​​​返回​​null​​​时,​​ThreadFactory​​将无法创建线程,此时执行器继续运行,但是可能无法执行任何任务。线程应该拥有modifyThread​RuntimePermission​​。如果工作线程或者其它线程使用不具有该权限的线程池,服务可能被降级:配置变更可能不会及时生效,且关闭线程池可能会保留终止但未完成的状态。

线程保持存活时间

如果线程池当前拥有多于​​corePoolSize​​​数量的线程,则空闲时间超过​​keepAliveTime​​​(可通过​​getKeepAliveTime(TimeUnit)​​​方法获取)的线程将被终止,以减少资源消耗。可以通过​​setKeepAliveTime(long,TimeUnit)​​​方法动态改变该参数值。使用​​setKeepAliveTime(Long.MAX_VALUE, NANOSECONDS)​​​可以有效的阻止空闲线程在关闭之前终止。默认情况下,​​keep-alive​​​策略仅在线程池中线程数多余​​corePoolSize​​​时起作用。​​keepAliveTime​​​的值不为0的情况下,可通过​​allowCoreThreadTimeOut(boolean)​​​方法将​​keep-alive​​策略应用于核心线程。

排队(Queuing)

​BlockingQueue​​用于传输和容纳提交的任务。此队列的使用与线程池大小变化相关:

  • 如果线程池中当前线程数少于​​corePoolSize​​​,那么​​Executor​​总是优先创建新线程来处理任务请求,而不是让任务请求排队
  • 如果线程池中当前线程数等于或者多余​​corePoolSize​​​,那么​​Executor​​总是优先让任务排队,而不是创建新线程
  • 如果无法让任务请求排队(比如任务队列已满),且线程池中当前线程数未超过​​maximumPoolSize​​,则创建一个新线程来处理任务请求,否则将拒绝该任务请求

三种排队策略:

  • 直接传递(Direct handoffs)
    ​​​SynchronousQueue​​​是工作队列(​​workQueue​​)的一个默认好选择。它将任务交给线程,而不是保留它们。此时,如果没有立即可用的线程,将构造新线程,因为让任务排队的尝试将会失败。此策略在处理可能具有内部依赖关系的请求集时避免锁定。通常需要无界的​​maximumPoolSize​​,以避免拒绝新任务的提交。这反过来说明当任务平均提交速度持续大于平均处理速度时,线程数无限增长的可能性。如果使用​​newCachedThreadPool​​创建线程池则表示使用直接传递策略
  • 无界队列(Unbounded queues)
    当所有核心线程都繁忙时,使用无界队列(例如,没有预定义容量的​​LinkedBlockingQueue​​)将导致新任务在队列中等待,从而导致没有多余​​corePoolSize​​的线程被创建(​​maximumPoolSize​​的值不起任何作用)。当每个任务完全彼此独立,互不影响执行时,这可能是合适的。例如,在网页服务器中, 这种排队方式用于平滑瞬时大量请求时很有用。需要注意的是,当任务平均提交速度持续大于平均处理速度时,可能会导致无界队列无限增长。如果使用​​newFixedThreadPool​​ 创建线程池则表示使用无界队列。
  • 有界队列(Bounded queues)
    有界队列(例如,​​ArrayBlockingQueue​​)配合​​maximumPoolSizes​​使用有助于防止资源耗尽,但是难以调整和控制。队列大小和最大线程池大小需要相互权衡:使用大队列和较小的线程池可以最大限度地减少CPU使用率,操作系统资源和上下文切换开销,但是会导致人为的低吞吐量。如果任务频繁被阻塞(比如I/O限制),那么系统可以调度比我们允许的更多的线程。使用小队列通常需要较大的线程池,这会让CPU保持繁忙,但可能会产生不可接受的调度开销,这也会降低吞吐量。

拒绝处理任务

当​​Executor​​​已关闭、使用有界的线程池、工作队列,且达到最大值时,通过方法​​execute(Runnable)​​​提交的任务将被拒绝。在任何一种情况下,​​execute​​​方法调用其​​RejectedExecutionHandler​​​的​​rejectedExecution(Runnable,ThreadPoolExecutor)​​​方法。提供以下4种预定义处理策略:
​​​ThreadPoolExecutor.AbortPolicy​​​(默认策略)
拒绝任务时,处理器会抛出一个运行时异常​​​RejectedExecutionException​​。

​ThreadPoolExecutor.CallerRunsPolicy​​​ 调用​​execute​​的线程自己运行任务。这提供了一个简单的反馈控制机制,将会降低新任务提交的速率。

​ThreadPoolExecutor.DiscardPolicy​​ 不能被执行的任务将被抛弃

​ThreadPoolExecutor.DiscardOldestPolicy​​​ 如果​​Executor​​已关闭,工作队列队首的任务被丢弃,然后重试执行。(重试也可能失败,导致重复执行前面的动作)

可以定义和使用其他类型的​​RejectedExecutionHandler​​类。这样做需要一些谨慎,特别是当策略被设计为仅在特定容量或者队列策略下有效时

线程运行状态

该线程池使用了一个​​runState​​来对线程进行主要生命周期控制,具有以下值:

​RUNNING​​​: 接收新任务并且处理排队的任务
​​​SHUTDOWN​​​: 不接收新任务,但是处理排队的任务。
​​​STOP​​​: 不接收新任务,不处理排队的任务,并且中断正在进行的任务。
​​​TIDYING​​​: 所有任务已终止。​​workerCount​​​为0。线程转为​​TIDYING​​​状态将会运行​​terminated()​​​ hook方法。
​​​TERMINATED​​​: ​​terminated()​​已经运行完。

这些值之间的数字顺序很重要,为了支持有序比较,​​runState​​会随着时间单调递增,但不需要达到每个状态。

状态转换如下:
​​​RUNNING​​​ -> ​​SHUTDOWN​​​ 调用​​shutdown()​​时,可能隐式的在​​finalize()​​中调用

​RUNNING​​​ 或者 ​​SHUTDOWN​​​ -> ​​STOP​​​ 调用​​shutdownNow()​​时

​SHUTDOWN​​​ -> ​​TIDYING​​​ 当工作队列和线程池都为空时
​STOP​​ -> ​​TIDYING​​ 线程池为空时

​TIDYING​​​ -> ​​TERMINATED​​​ 当​​terminated()​​ hook方法运行完成时。

线程的析构(Finalization)

如果线程池不再被程序引用且没有剩余的线程,线程池将被关闭。如果希望确保未被引用的线程池被回收,即使用户用户忘记调用​​shutdown​​​,则必须通过适当的keep-alive配置,使用更低的下限--0核心线程数或者设置​​allowCoreThreadTimeOut(boolean)​​,确保未使用的线程最终会消亡。

作者:​​授客​

标签:Java,队列,创建,maximumPoolSize,corePoolSize,任务,线程,ThreadPoolExecutor
From: https://blog.51cto.com/shouke/5827481

相关文章

  • JavaScript 学习-50.实现页面菜单拖放(Drag 和 Drop)
    前言拖放是一种常见的操作,即抓取对象以后从一个位置拖到另一个位置。在HTML5中,拖放是标准的一部分,任何元素都能够拖放。拖放(Drag和Drop)在拖曳操作中,被拖曳的元素称做源......
  • Java学习File类的判断和获取功能
    方法名说明publicbooleanisDirectory()测试此抽象路径名表示的File是否为目录publicbooleanisFile()测试此抽象路径名表示的File是否为文件publicbooleanexists()测试......
  • 自学Java的学习步骤与基本态度
     一般来说,刚开始学Java,需要掌握的基础并没有那么多,但是却需要牢牢掌握。如:Java数据类型、String基本类型封装类、MySQL等基础知识,属于必备技能,不论你是什么目的学习Java,此......
  • PyCharm在Linux安装出现报错-Java Runtime (class file version 55.0)
        在Linux桌面下安装PyCharm的时候出现如下报错root@ubuntu:~#cdpycharm-community-2021.1.1root@ubuntu:~/pycharm-community-2021.1.1#lsbinclas......
  • 线程和进程,并发和并行的区别
    线程和进程计算机教材上的经典定义如下:线程是操作系统调度的基本单位;进程是操作系统资源分配的基本单位。线程和进程属于一个抽象的概念,具体实现还得看具体的操作系统......
  • SAP Java Connector 正常运行所需的网络配置
    SAPJCO在本地安装成功并且将目录加到PATH环境变量后,运行命令行:java-jarsapjco3.jar如果看到下列弹出窗口,说明JCO配置成功。JCo使用基于TCP/IP的CPI-C协议......
  • 多线程的异常处理
    1.异常在线程内部处理多线程使用过程中,在线程内部使用try...catch...是可以捕获异常的。但是外部使用try...catch...通常无法捕获异常,也就是说程序不会throw异常(异常被吞......
  • JAVA开发搞了一年多的大数据,究竟干了点啥
    JAVA开发搞了一年多大数据的总结​ 2021年7月份加入了当前项目组,以一个原汁原味的Java开发工程师的身份进来的,来了没多久,项目组唯一一名大数据开发工程师要离职了,一时间一......
  • 最新版Jenkins(jdk11)-----JAVA项目使用低版本jdk编译的解决办法
    背景开源Devops工具Jenkins宣布:从6月28日发布的Jenkins2.357和即将发布的9月LTS版本开始,Jenkins最低需要Java11。所以,你懂得,很坑,项目只是jdk1.8解决......
  • java IO流
    java文件创建:(1)第一种创建方式(2)第二种创建方式 (3)第三种创建方式  (4)文件信息的查询操作  (5)文件夹的创建file.mkdir()创建文件夹成功返回true,失败返回false......