线程池为什么不推荐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。