Java 线程池
概念:管理线程的池子
相比于手工创建、运行线程,使用线程池有如下优点:
- 降低线程创建和销毁线程造成的开销。
- 提高响应速度。任务到达时,相对于手工创建一个线程,直接从线程池中拿线程,速度肯定快很多。
- 提高线程可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统稳定性。使用线程池可以进行统一分配、调优和监控。
Java 线程池创建
无论是创建何种类型线程池( FixedThreadPool 、CachedThreadPool 等 ),均会调用 ThreadPoolExecutor 构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
参数说明
参数 | 描述 |
---|---|
corePoolSize | 线程池中常驻线程的最大数量。这表示即使空闲时,线程池中也会保持的线程数。 |
maximumPoolSize | 线程池中允许存在的最大线程数,包括核心线程和非核心线程。 |
keepAliveTime | 非核心线程空闲时等待新任务的最长时间,超过这个时间将被终止。 |
unit | 用于 keepAliveTime 参数的时间单位。 |
workQueue | 存放任务的阻塞队列,在任务被线程执行前等待存放。 |
handler | 当线程池达到最大容量且无法执行新任务时,处理饱和状态的策略。 |
线程池执行流程
当提交一个新任务,线程池的处理流程如下:
- 判断线程池中核心线程数是否已达阈值
corePoolSize
,若否,则创建一个新核心线程执行任务。 - 若核心线程数已达阈值
corePoolSize
,判断阻塞队列workQueue
是否已满,若未满,则将新任务添加进阻塞队列。 - 若阻塞队列已满,再判断线程池中线程数是否达到阈值
maximumPoolSize
。若未达到,则新建一个非核心线程执行任务;若已达到,则执行线程池饱和策略。
线程池饱和策略包括以下几种:
AbortPolicy
:直接抛出一个异常,这是默认策略。DiscardPolicy
:直接丢弃任务。DiscardOldestPolicy
:抛弃下一个将要被执行的任务(最旧任务)。CallerRunsPolicy
:在主线程中执行任务。
常用的线程池
典型的工作队列
- ArrayBlockingQueue:使用数组实现的有界阻塞队列,特性先进先出。
- LinkedBlockingQueue:使用链表实现的阻塞队列,特性先进先出,可以设置其容量,默认为
Integer.MAX_VALUE
。 - PriorityBlockingQueue:使用平衡二叉树堆实现的具有优先级的无界阻塞队列。
- DelayQueue:无界阻塞延迟队列,队列中每个元素均有过期时间。当从队列获取元素时,只有过期元素才会出队列。队列头元素是最快要过期的元素。
- SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程调用移除操作,否则插入操作一直处于阻塞状态。
典型的线程池
SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
创建单个线程。
它适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。
SingleThreadExecutor
的 corePoolSize
和 maximumPoolSize
被设置为 1,使用无界队列 LinkedBlockingQueue
作为线程池的工作队列。
执行流程:
- 当线程池中没有线程时,会创建一个新线程来执行任务。
- 当前线程池中有一个线程后,将新任务加入
LinkedBlockingQueue
。 - 线程执行完第一个任务后,会在一个无限循环中反复从
LinkedBlockingQueue
获取任务来执行。
使用场景:适用于串行执行任务场景。
FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads,
ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads,
nThreads,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
corePoolSize
等于 maximumPoolSize
,所以线程池中只有核心线程,使用无界阻塞队列 LinkedBlockingQueue
作为工作队列。
FixedThreadPool
是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭。当所有的线程都处于活动状态时,新的任务都会处于等待状态,直到有线程空闲出来。
执行流程:
- 如果当前运行的线程数少于
corePoolSize
,则创建新线程来执行任务。 - 在线程数目达到
corePoolSize
后,将新任务放到LinkedBlockingQueue
阻塞队列中。 - 线程执行完任务后,会在循环中反复从
LinkedBlockingQueue
获取任务来执行。
使用场景:适用于处理 CPU 密集型的任务,确保 CPU 在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。
CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
核心线程数为 0 ,总线程数量阈值为 Integer.MAX_VALUE
,即可以创建无限的非核心线程。
执行流程:
- 先执行
SynchronousQueue
的offer
方法提交任务,并查询线程池中是否有空闲线程来执行SynchronousQueue
的poll
方法来移除任务。如果有,则配对成功,将任务交给这个空闲线程。 - 否则,配对失败,创建新的线程去处理任务。
- 当线程池中的线程空闲时,会执行
SynchronousQueue
的poll
方法等待执行SynchronousQueue
中新提交的任务。若等待超过60秒,空闲线程就会终止。
使用场景:执行大量短生命周期任务。因为 maximumPoolSize
是无界的,所以如果提交任务的速度大于线程池中线程处理任务的速度就要不断创建新线程;每次提交任务,都会立即有线程去处理,因此 CachedThreadPool
适用于处理大量、耗时少的任务。
ScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize,
Integer.MAX_VALUE,
0,
NANOSECONDS,
new DelayedWorkQueue());
}
线程总数阈值为 Integer.MAX_VALUE
,工作队列使用 DelayedWorkQueue
,非核心线程存活时间为0,所以线程池仅仅包含固定数目的核心线程。
两种方式提交任务:
- scheduleAtFixedRate:按照固定速率周期执行
- scheduleWithFixedDelay:上个任务延迟固定时间后执行
使用场景:周期性执行任务,并且需要限制线程数量的场景