前言
在开发过程中经常会使用线程池来异步处理任务,使用线程池最重要的原因就是其能复用线程,线程执行完毕一个任务了以后,还能继续执行后续的任务,对比直接新建线程无疑减少了大量开销。本文主要梳理下线程池的一些参数及作用。
参数作用
经过查看 ThreadPoolExecutor 的构造器,发现其最长构造器签名如下。
public ThreadPoolExecutor(int corePoolSize, |
corePoolSize => 最大核心线程数
maximumPoolSize => 最大线程数
keepAliveTime => 空闲线程存活时间
unit => 空闲线程存活时间单位
workQueue => 任务阻塞队列
threadFactory => 线程工厂
handler => 拒绝执行处理器
任务阻塞队列
一般有以下三种阻塞队列:
- SynchronousQueue 该阻塞队列容量为 0 ,放入一个任务就会阻塞,直到被取走。线程池使用该阻塞队列可以使任务立即被执行,除非达到了最大线程数执行拒绝策略。一般而言会配合最大线程数 Integer.MAX_VALUE 使用。
- LinkedBlockingQueue 该阻塞队列容量为无限大,放入任务不会阻塞。线程池使用该阻塞队列那么只要达到了最大核心线程数,就不会再创建线程,也就是说不会创建工作线程。
- ArrayBlockingQueue 该阻塞队列有容量限制。线程池使用该阻塞队列可以防止资源耗尽,因为其不会导致任务不断的堆积在阻塞队列中。
拒绝执行处理器
有以下几种已定义的处理器:
- AbortPolicy 直接抛出异常
- DiscardPolicy 直接删除当前无法执行的任务
- DiscardOldestPolicy 删除最老放入队列的任务
- CallerRunsPolicy 由提交任务的线程自己处理该任务
工作原理
线程池创建后默认并没有任何线程启动,如果需要预启动核心线程可以调用以下方法:
prestartCoreThread(); |
当一个任务提交时,可能会遇到如下两种情况:
- 如果当前线程数少于最大核心线程数,那么会创建一个核心线程执行该任务(不管是否有其它线程空闲)。
- 如果当前线程数等于最大核心线程数,那么会将当前任务放入到阻塞队列中去,如果当前阻塞队列已满,则又会遇到如下两种情况:
- 当前线程数小于最大线程数,那么创建一个非核心线程执行该任务。
- 当前线程数等于最大线程数,那么执行拒绝执行处理器。
如果线程空闲超过指定时间,那么就会被回收,默认只对非核心线程生效,如果需要对核心线程也生效可以调用以下方法:
allowCoreThreadTimeOut(); |
判断一个线程是否空闲,就是其是否能在指定时间内从阻塞队列中取出一个任务执行。
快速创建
Java 提供了 Executors 类用于快速创建线程池,其含有如下几种创建线程池的方法。
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}创建一个最多只能有 n 个线程的线程池,且线程都为核心线程。该线程池中最大线程数就是给定的数量,核心线程数与最大线程数一致,同时采用了 LinkedBlockingQueue 来阻止新的线程被创建。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}创建一个最多只能有一个线程的线程池,其线程都为核心线程。该线程池核心线程数与最大线程数一致都为 1,同时采用了 LinkedBlockingQueue 来阻止新的线程被创建,其实也就是说使用该线程是串行执行任务的。
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}创建一个无最大线程数的线程池,其线程都为非核心线程。其使用了 SychronousQueue 来保证每次提交任务都会被立即执行。
newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}创建指定核心线程数无限工作线程数的线程池。它一般用于执行定时任务。
不过阿里不推荐使用上述几种方式创建线程池,原因在于 newFixedThreadPool 以及 newSingleThreadExecutor 由于其阻塞队列无容量限制,可能会导致任务不断堆积最终导致 OOM,对于 newCachedThreadPool 以及 newScheduledThreadPool 由于其无最大线程数限制,可能导致线程创建过多耗尽资源造成卡顿或者 OOM。在开发中还是采用明确调用 ThreadPoolExecutor 构造器的方式创建线程池,这样不管对于写代码的人还是看代码的人都能清楚的知道该线程池可能存在的问题。