线程池为什么不推荐Executors创建
- 前言
- 内部四种类型线程池的创建方法
- 常见线程池参数总结
- 三个重要参数
- 四个其他参数
- 四种饱和策略
- Executors源码
- 出现OOM问题分析
- 总结
前言
之前面试的时候只知道推荐使用ThreadPoolExecutor的构造方法来创建线程池,使用Executors创建线程池可能会报OOM异常,但是不知道为什么会报这个,现在一篇文章彻底弄懂
内部四种类型线程池的创建方法
FixedThreadPool : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
SingleThreadExecutor: 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
CachedThreadPool: 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
ScheduledThreadPoolExecutor:是一个使用线程池执行定时任务的类,相较于Java中提供的另一个执行定时任务的类Timer,其主要有如下两个优点:
使用多线程执行任务,不用担心任务执行时间过长而导致任务相互阻塞的情况,Timer是单线程执行的,因而会出现这个问题;
不用担心任务执行过程中,如果线程失活,其会新建线程执行任务,Timer类的单线程挂掉之后是不会重新创建线程执行后续任务的。
常见线程池参数总结
三个重要参数
- corePoolSize:核心线程数,就是最小可以同时运行的线程数量
- maximumPoolSize:当队列中存放的任务达到队列容量的时候,当前可以同时运行的最大线程数量就编程最大线程数
- workQueue:当新任务来的时候,判断正在运行的线程数是否达到了核心线程数,达到了就把新的任务存放到队列中
四个其他参数
- keepAliveTime:线程池中的线程数大于核心线程数的时候,不会立即销毁,而是等待keepAliveTime时间才会销毁。
- unit:keepAliveTIme参数的时间单位
- threadFactory:executor创建线程会用到
- handler:饱和策略
四种饱和策略
- AbortPolicy:抛出异常来拒绝新任务的处理
- CallerRunsPolicy:哪个线程创建了线程池,这个线程来执行被拒绝的任务
- DiscardPolicy:不处理新任务,直接丢弃
- DiscardOldestPolicy:丢弃最早的未处理的任务请求
Executors源码
public class Executors {
/**
* 创建固定数量线程的线程池
*
* 这是最后一个参数--阻塞队列调用的方法,长度是Integer.MAX_VALUE
* public LinkedBlockingQueue() {
* this(Integer.MAX_VALUE);
* }
*
* @param nThreads the number of threads in the pool
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
* 创建只有一个线程的线程池
*
* 这是最后一个参数--阻塞队列调用的方法,长度也是Integer.MAX_VALUE
* public LinkedBlockingQueue() {
* this(Integer.MAX_VALUE);
* }
*
* @return the newly created single-threaded Executor
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
/**
*创建一个缓冲线程池
*
* 这是最后一个参数--阻塞队列调用的方法
* public SynchronousQueue() {
* this(false);
* }
*
* 它的第二个参数,maximumPoolSize 为Integer.MAX_VALUE
*
* @return the newly created thread pool
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
/**
* Creates a thread pool that can schedule commands to run after a
* given delay, or to execute periodically.
*
* 创建一个可以在给定延迟后再执行或定期执行命令的线程池
*
* ScheduledThreadPoolExecutor 是 ThreadPoolExecutor 的子类,代码如下:
*
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor
implements ScheduledExecutorService {
//这是下面调用的构造方法,其实是调用了父类的构造方法,这些参数都是下面分析的参数
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
}
*
* @param corePoolSize the number of threads to keep in the pool,
* even if they are idle
* @return a newly created scheduled thread pool
* @throws IllegalArgumentException if {@code corePoolSize < 0}
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
}
出现OOM问题分析
Executors中创建线程池:
FixedThreadPool 和 SingleThreadExecutor 传入的最后一个参数阻塞队列 ”workQueue“,默认的长度是INTEGER.MAX_VALUE,而它们允许的最大线程数量又是有限的,所以当请求线程的任务过多线程不够用时,它们会在队列中等待,又因为队列的长度特别长,所以可能会堆积大量的请求,导致OOM。
CachedThreadPool 和 ScheduledThreadPool 它们的阻塞队列长度有限,但是传入的第二个参数maximumPoolSize 为Integer.MAX_VALUE,这就意味着当请求线程的任务过多线程不够而且队列也满了的时候,线程池就会创建新的线程,因为它允许的最大线程数量是相当大的,所以可能会创建大量线程,导致OOM。
总结
FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致 OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。