# 使用线程池的好处
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
# 线程池的工作原理
当线程池中有任务需要执行时,线程池会判断如果线程数量没有超过核心数量就会新建线程池进行任务执行,如果线程池中的线程数量已经超过核心线程数,这时候任务就会被放入任务队列中排队等待执行;如果任务队列超过最大队列数,并且线程池没有达到最大线程数,就会新建线程来执行任务;如果超过了最大线程数,就会执行拒绝执行策略。
# 创建线程池的方式
# Executors 创建线程池
Executors 执行器创建线程池很多基本上都是在 ThreadPoolExecutor
构造方法上进行简单的封装,特殊场景根据需要自行创建。可以把Executors理解成一个工厂类 。
方法 | 特点 | 工作机制 | 使用场景 |
---|---|---|---|
newFixedThreadPool | 1. 这类线程池的特点就是里面全是核心线程,没有非核心线程,也没有超时机制,keepAliveTime 在这里是无效的,任务大小也是没有限制的,2. 数量固定,即使是空闲状态,线程不会被回收,除非线程池被关闭,从构造方法也可以看出来,只有两个参数,一个是指定的核心线程数,一个是线程工厂, 3. 任务队列采用了无界的阻塞队列 LinkedBlockingQueue | 1. 如果线程数少于核心线程, 创建核心线程执行任务 2. 如果线程数等于核心线程, 把任务添加到 LinkedBlockingQueue 阻塞队列 ,如果线程执行完任务,去阻塞队列取任务,继续执行。 | 适用于任务量比较固定但耗时长的任务 |
newCacheThreadPool | 1. 这类线程池的特点就是里面没有核心线程,全是非核心线程, 其maximumPoolSize 设置为Integer.MAX_VALUE ,线程可以无限创建,当线程池中的线程都处于活动状态的时候,线程池会创建新的线程来处理新任务,否则会用空闲的线程来处理新任务,2. 这类线程池的空闲线程都是有超时机制的, keepAliveTime 在这里是有效的,时长为60秒,超过60秒的空闲线程就会被回收,3. 当线程池都处于闲置状态时,线程池中的线程都会因为超时而被回收,所以几乎不会占用什么系统资源。 4. 任务队列采用的是 SynchronousQueue ,这个队列是无法插入任务的,一有任务立即执行. | 1. 因为没有核心线程,所以任务直接加到SynchronousQueue 队列。 2. 判断是否有空闲线程,如果有,就去取出任务执行。 如果没有空闲线程,就新建一个线程执行。 3. 执行完任务的线程,还可以存活60秒,如果在这期间,接到任务,可以继续活下去;否则,被销毁。 | 用于并发执行大量短期的小任务。 |
newScheduledThreadPool | 1. 这类线程池核心线程数量是固定的,和FixThreadPool 有点像,但是它的非核心线程是没有限制的,并且非核心线程一闲置就会被回收。2. keepAliveTime 同样无效,因为核心线程是不会回收的,3. 任务队列采用的 DelayQueue 是个无界的队列,延时执行队列任务。 | 1. 当运行的线程数没有达到corePoolSize 的时候,就新建线程去DelayQueue 中取ScheduledFutureTask ,然后才去执行任务, 2. 否则就把任务添加 DelayQueue ,DelayQueue 会将任务排序,新建一个非核心线程顺序执行,执行完线程就回收,然后循环。 | 适用于执行定时任务和具体固定周期的重复任务 |
newSingleThreadExecutor | 1. 只有一个核心线程的线程池,从构造方法来看,它可以单独执行,也可以与周期线程池结合用。 2. 其任务队列是 LinkedBlockingQueue ,这是个无界的阻塞队列,因为线程池里只有一个线程,就确保所有的任务都在同一个线程中顺序执行。 | 1. 如果线程池没有一条线程,新建线程执行任务 2. 如果有,将任务加到阻塞队列当前的唯一线程,从队列取任务,执行完一个,再继续执行下一个。 | 适用于串行执行任务的场景,一个任务一个任务地执行。 |
newSingleThreadScheduledExecutor | 此线程池就是单线程的newScheduledThreadPool 。 | ||
newWorkStealingPool | Java 8 新增创建线程池的方法,创建时如果不设置任何参数,则以当前机器CPU 处理器数作为线程个数,此线程池会并行处理任务,不能保证执行顺序。 |
# ThreadPoolExecutor创建线程池
Executors
中创建线程池的快捷方法,实际上是调用了ThreadPoolExecutor
的构造方法(定时任务使用的是ScheduledThreadPoolExecutor
),该类构造方法参数列表如下:
// Java线程池的完整构造函数
public ThreadPoolExecutor(
int corePoolSize, // 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。
int maximumPoolSize, // 线程数的上限
long keepAliveTime, TimeUnit unit, // 超过corePoolSize的线程的idle时长,
// 超过这个时间,多余的线程会被回收。
BlockingQueue<Runnable> workQueue, // 任务的排队队列
ThreadFactory threadFactory, // 新线程的产生方式
RejectedExecutionHandler handler); // 拒绝策略
参数解释如下:
参数 | 含义 |
---|---|
corePoolSize | 中的核心线程数,默认情况下核心线程一直存活在线程池中,如果将ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设为 true,如果线程池一直闲置并超过了 keepAliveTime 所指定的时间,核心线程就会被终止。 |
maximumPoolSize | 最大线程数,当线程不够时能够创建的最大线程数。 |
keepAliveTime | 线程池的闲置超时时间,默认情况下对非核心线程生效,如果闲置时间超过这个时间,非核心线程就会被回收。如果 ThreadPoolExecutor 的 allowCoreThreadTimeOut 设为 true 的时候,核心线程如果超过闲置时长也会被回收。 |
unit | 配合 keepAliveTime 使用,用来标识 keepAliveTime 的时间单位。可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。 |
workQueue | 线程池中的任务队列,使用 execute() 或 submit() 方法提交的任务都会存储在此队列中。 |
threadFactory | 为线程池提供创建新线程的线程工厂。 |
rejectedExecutionHandler | 线程池任务队列超过最大值之后的拒绝策略,RejectedExecutionHandler 是一个接口,里面只有一个 rejectedExecution 方法,可在此方法内添加任务超出最大值的事件处理。 |
# 线程池的任务队列
任务队列,用于保持或等待执行的任务阻塞队列。BlockingQueue
的实现类即可,有无界队列和有界队列
队列 | 说明 |
---|---|
ArrayBlockingQueue | 基于数组结构的有界队列,此队列按FIFO原则对元素进行排序 |
LinkedBlockingQueue | LinkedBlockingQueue (可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE ,吞吐量通常要高于ArrayBlockingQuene ;newFixedThreadPool 线程池使用了这个队列。 |
DelayQueue | DelayQueue (延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool 线程池使用了这个队列。 |
SynchronousQueue | SynchronousQueue (同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene ,newCachedThreadPool 线程池使用了这个队列。 |
PriorityBlockingQueue | PriorityBlockingQueue (优先级队列)是具有优先级的无界阻塞队列; |
# 线程池提交任务的方式
线程池框架提供了两种方式提交任务,submit()
和execute()
,通过submit()
方法提交的任务可以返回任务执行的结果,通过execute()
方法提交的任务不能获取任务执行的结果。
提交方式 | 是否关心结果 |
---|---|
Future<T> submit(Callable<T> task) | 是 |
void execute(Runnable command) | 否 |
Future<?> submit(Runnable task) | 否,但是其get() 总是返回null |
# 线程池异常处理
在使用线程池处理任务的时候,任务代码可能抛出RuntimeException
,抛出异常后,线程池可能捕获它,也可能创建一个新的线程来代替异常的线程,我们可能无法感知任务出现了异常,因此我们需要考虑线程池异常情况。可以考虑使用以下方式:
在任务代码
try/catch
捕获异常。submit
执行,通过Future
对象的get方法接收抛出的异常,再处理。实例化时,传入自己的
ThreadFactory
,为工作者线程设置UncaughtExceptionHandler
,在uncaughtException
方法中处理异常。重写
ThreadPoolExecutor
的afterExecute
方法,处理传递的异常引用。
# 线程池拒绝策略
ThreadPoolExecutor
也提供了 4 种默认的拒绝策略:
拒绝策略 | 说明 |
---|---|
DiscardPolicy() | 丢弃掉该任务,不进行处理。 |
DiscardOldestPolicy() | 丢弃队列里最近的一个任务,并执行当前任务。 |
AbortPolicy() | 直接抛出 RejectedExecutionException 异常(默认)。 |
CallerRunsPolicy() | 既不抛弃任务也不抛出异常,直接使用主线程来执行此任务。 |
# 线程池状态
线程池有这几个状态:RUNNING
,SHUTDOWN
,STOP
,TIDYING
,TERMINATED
。
状态 | 特点 |
---|---|
RUNNING | 该状态的线程池会接收新任务,并处理阻塞队列中的任务; 调用线程池的 shutdown() 方法,可以切换到SHUTDOWN状态; 调用线程池的 shutdownNow() 方法,可以切换到STOP状态; |
SHUTDOWN | 该状态的线程池不会接收新任务,但会处理阻塞队列中的任务; 队列为空,并且线程池中执行的任务也为空,进入TIDYING状态; |
STOP | 该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务; 线程池中执行的任务为空,进入TIDYING状态; |
TIDYING | 该状态表明所有的任务已经运行终止,记录的任务数量为0。 terminated()执行完毕,进入TERMINATED状态 |
TERMINATED | 该状态表示线程池彻底终止 |
# 线程池常用方法
ThreadPoolExecutor
有如下常用方法:
submit()/execute()
:执行线程池shutdown()/shutdownNow()
:终止线程池- 使用
shutdown()
程序不会报错,也不会立即终止线程,它会等待线程池中的缓存任务执行完之后再退出,执行了shutdown()
之后就不能给线程池添加新任务了; shutdownNow()
会试图立马停止任务,如果线程池中还有缓存任务正在执行,则会抛出java.lang.InterruptedException: sleep interrupted
异常。
- 使用
isShutdown()
:判断线程是否终止getActiveCount()
:正在运行的线程数getCorePoolSize()
:获取核心线程数getMaximumPoolSize()
:获取最大线程数getQueue()
:获取线程池中的任务队列allowCoreThreadTimeOut(boolean)
:设置空闲时是否回收核心线程