Java 线程池之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 线程池之ThreadPoolExecutor学习总结的更多相关文章
- Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析
目录 引出线程池 Executor框架 ThreadPoolExecutor详解 构造函数 重要的变量 线程池执行流程 任务队列workQueue 任务拒绝策略 线程池的关闭 ThreadPoolEx ...
- 【Java 多线程】Java线程池类ThreadPoolExecutor、ScheduledThreadPoolExecutor及Executors工厂类
Java中的线程池类有两个,分别是:ThreadPoolExecutor和ScheduledThreadPoolExecutor,这两个类都继承自ExecutorService.利用这两个类,可以创建 ...
- 学习java 线程池-1: ThreadPoolExecutor
1. Executor 该接口内只有一个接口方法 :该方法的目的就是执行指定的 Runnable (但会不会执行,或者会不会立马执行,则不一定.因为要取决于整个线程池的状态) Executor 中文的 ...
- Java线程池之ThreadPoolExecutor
前言 线程池可以提高程序的并发性能(当然是合适的情况下),因为对于没有线程的情况下,我们每一次提交任务都新建一个线程,这种方法存在不少缺陷: 1. 线程的创建和销毁的开销非常高,线程的创建需要时间, ...
- 深入理解Java线程池:ThreadPoolExecutor
线程池介绍 在web开发中,服务器需要接受并处理请求,所以会为一个请求来分配一个线程来进行处理.如果每次请求都新创建一个线程的话实现起来非常简便,但是存在一个问题: 如果并发的请求数量非常多,但每个线 ...
- java线程池的使用学习
目录 1. 线程池的创建 2. 线程池的运行规则 3. 线程池的关闭 4. 线程池的使用场合 5. 线程池大小的设置 6 实现举例 1. 线程池的创建 线程池的创建使用ThreadPoolExecut ...
- Java线程池定制ThreadPoolExecutor官方定制实例
1.仍然先看构造方法:ThreadPoolExecutor构造方法 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,lon ...
- java 线程池 ExeutorService
Java线程池 ExecutorService 原文:https://blog.csdn.net/suifeng3051/article/details/49443835/ 本篇主要涉及到的是java ...
- 深入理解Java线程池:ScheduledThreadPoolExecutor
介绍 自JDK1.5开始,JDK提供了ScheduledThreadPoolExecutor类来支持周期性任务的调度.在这之前的实现需要依靠Timer和TimerTask或者其它第三方工具来完成.但T ...
- Java线程池 ExecutorService
一.ExecutorService介绍 ExecutorService是Java中对线程池定义的一个接口,它java.util.concurrent包中,在这个接口中定义了和后台任务执行相关的方法: ...
随机推荐
- Windows中实现将bat或exe文件作为服务_且实现命令行安装、配置、启动、删除服务
一.背景描述 在Windows环境下进行日常的项目开发过程中,有时候需要将bat文件或exe文件程序注册为Windows的服务实现开机自己运行(没有用户登陆,服务在开机后也可以照常运行).且对于那些没 ...
- no implicit conversion of nil into String
一.Cocoapod 执行pod install命令时报错 [!] An error occurred while processing the post-install hook of the Po ...
- WNS 后台Push服务调试脚本
一.API说明 https://cloud.tencent.com/document/product/276/3212 二.推送脚本 #!/usr/local/bin/python3 # -*- ...
- mediaserverd
1.mediaserverd是什么 mediaserverd(/usr/sbin/mediaserverd)是被root进程launchd启动的一个后台(daemon)进程,其描述文件为com.app ...
- 关于ICMP隧道一点理解(起于修改wien-qq的记住密码)
起 使用linux半年多以来,一直有一个我很需要但我无法完美解决的东西困扰这我-----(linux QQ) 目前我的解决方案是GitHub上的一个第三方QQ(有关ICMP隧道的搭建见 承,转) 但是 ...
- 【Socket】解决TCP粘包问题
一.介绍 TCP一种面向连接的.可靠的.基于字节流的传输层协议. 三次握手: 客户端发送服务端连接请求,等待服务端的回复. 服务端收到请求,服务端回复客户端,可以建立连接,并等待. 客户端收到回复并发 ...
- Win11 LTSC 中文版来了,丝般顺滑,极速响应
最近网络上出现了泄露的Win11的LTSC版本,版本号为Build 26100.1,据息,该泄露版是微软提供给OEM厂商测试用的,是今年下半年的Windows 11 LTSC RTM版的正式版本,却被 ...
- LNMP集群架构
网站集群拆分 上一节我们是部署了单机的LNMP,再往下,要进行拆分了,无论是性能.还是安全性,都务必要拆分. 拆分的内容有 nginx集群 mysql nfs共享存储 等 拆分思路 情况1 当前的单机 ...
- 小白也能玩转Git:从入门到实战详细教程
Git介绍 Git是一种分布式版本控制系统,它广泛应用于软件开发中.通过Git,开发人员可以追踪文件的变化.协作工作.管理代码库等.与集中式版本控制系统(如SVN)不同,Git使每个开发人员都具有完整 ...
- 关于Elasticsearch查找相关的问题汇总(match、match_phrase、query_string和term)
关于Elasticsearch查找相关的问题汇总(match.match_phrase.query_string和term) 查询全部: { "query": { "ma ...