几种常见的线程池
一、概述
如下图所示,Executor框架中线程池的类型有如下2种:ThreadPoolExecutor
、ScheduledThreadPoolExecutor
。
在Executors工具类中,提供了3种 ThreadPoolExecutor 类型的线程池。
- FixedThreadPool:一个可重用固定线程数的线程池
- SingleThreadExecutor:只有一个工作线程的线程池
- CachedThreadPool:是一个会根据需要创建新线程的线程池
二、ThreadPoolExecutor 线程池
线程池的参数含义请参考:Executor框架(二) — 线程池原理浅析 (线程池的创建)
2.1 FixedThreadPool
FixedThreadPool 是可重用固定线程数的线程池。
// Executors.class
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
corePoolSize
、maximumPoolSize
都被设置为指定的参数nThreads,意味着线程数固定。
keepAliveTime
设置为0L,意味着多余的空闲线程会被立即终止。
blockQueue
设置为 LinkedBlockingQueue 无界阻塞队列(LinkedBlockingQueues的默认容量为Integer.MAX_VALUE,可以认定为无界队列)。
FixedThreadPool.execute() 运行示意图如上图所示:
- 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。
- 在线程池当前运行的线程数等于corePoolSize后,会将任务加入LinkedBlockingQueue(无界阻塞队列)。
- 线程执行完1中的任务后,会在循环中反复从 LinkedBlockingQueue 获取任务来执行。
使用无界队列 LinkedBlockingQueue 作为线程池的工作队列的影响。
- 由于是无界队列,当线程池中的线程数达到corePoolSize后,新任务可以一直插入无界队列中,队列不会满,所以不会创建新线程 (即:线程数不会超过corePoolSize)。
- 由于是无界队列,所以参数 maximumPoolSize 不会生效。
- 由于是无界队列,在未设置allowCoreThreadTimeOut=true的条件下,keepAliveTime参数不会生效 (没有非核心的线程)。
- 由于是无界队列,在未关闭线程池前,不会执行拒绝任务。
2.2 SingleThreadExecutor
SingleThreadExecutor是一个单线程的线程池。
// Executors.class
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
SingleThreadExecutor.execute() 运行示意图如上图所示:
- 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。
- 在线程池当前运行的线程数等于corePoolSize后,会将任务加入LinkedBlockingQueue(无界阻塞队列)。
- 线程执行完1中的任务后,会在循环中反复从 LinkedBlockingQueue 获取任务来执行。
SingleThreadExecutor 使用无界队列作为工作队列,对线程池带来的影响与FixedThreadPool相同。
2.3 CachedThreadPool
CachedThreadPool 根据需要创建新线程的线程池。
// Executors.class
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
CachedThreadPool.execute() 运行示意图如上图所示:
-
首先执行
SynchronousQueue.offer(Runnable task)
。若当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)
,那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成;否则执行下面的步骤2。 -
当初始maximumPool为空,或者maximumPool中当前没有空闲线程时,将没有线程执行
SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)
。这种情况下,步骤1将失败,此时CachedThreadPool会创建一个新线程执行任务,execute()方法执行完成。 -
在步骤2中新创建的线程将任务执行完后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这个poll操作会让空闲线程最多在SynchronousQueue中等待60秒钟。如果60秒钟内没有回去新任务,则该空闲线程将终止。反之,则继续执行任务。
注: 由于空闲60秒的空闲线程会被终止,因此长时间保持空闲的CachedThreadPool不会使用任何资源(线程都会被销毁)。
三、ScheduledThreadPoolExecutor 线程池
ScheduledThreadPoolExecutor 主要功能有两个:
/**
* 达到给定的延时时间后,执行任务。这里传入的是实现Runnable接口的任务,
* 因此通过ScheduledFuture.get()获取结果为null
*/
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
/**
* 达到给定的延时时间后,执行任务。这里传入的是实现Callable接口的任务,
* 因此,返回的是任务的最终计算结果
*/
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
/**
* 以上一个任务开始的时间计时,period时间过去后,检测上一个任务是否执行完毕,
* 如果上一个任务执行完毕,则当前任务立即执行,
* 如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行。
*/
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
/**
* 当达到延时时间initialDelay后,任务开始执行。
* 上一个任务执行结束后到下一次任务执行,中间延时时间间隔为delay。
* 以这种方式,周期性执行任务。
*/
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
3.1 ScheduledThreadPoolExecutor 运行机制
// Executors.class
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
// ScheduledThreadPoolExecutor.class
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}
DelayedWorkQueue 是一个基于堆的数据结构(类似于DelayQueue和PriorityQueue),是一个无界队列。所以 maximumPoolSize=Integer.MAX_VALUE
在ScheduledThreadPoolExecutor 中不会生效。DelayedWorkQueue
ScheduledThreadPoolExecutor 的运行示意图如上图所示:
ScheduledThreadPoolExecutor的执行主要分为两大部分。
- 当调用
scheduleAtFixedRate()
方法或者scheduleWithFixedDelay()
方法时,会向ScheduledThreadPoolExecutor 的 DelayedWorkQueue 添加一个实现了
RunnableScheduledFutur接口的ScheduledFutureTask。 - 线程池中的线程从 DelayedWorkQueue 中获取 ScheduledFutureTask,然后执行任务。
为了实现周期性的执行任务,ScheduledThreadPoolExecutor 对 ThreadPoolExecutor做了如下修改。
- 使用 DelayedWorkQueue 作为任务队列。
- 获取任务的方式不同。
- 执行周期任务后,增加了额外的处理。
四、参考
《Java并发编程艺术》