Executors快捷创建线程池的潜在问题

写在前面

又是一个金九银十的面试季,相信很多小伙伴都会被问过创建线程有几种方式?有不少的博客都会回答有四种创建方式,分别是以下方式:
1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和Future创建线程
4)使用线程池例如用Executor框架

但是看过阿里巴巴开发规范手册的,都知道在手册里面写着 线程池不允许使用Executors去创建。

Executors的4种快捷创建线程池的方法

方法名 描述
newSingleThreadExecutor() 创建只有一个线程的线程池
newFixedThreadPool(int nThreads) 创建固定大小的线程池
newCachedThreadPool() 创建一个不限制数量的线程池
newScheduledThreadPool 创建一个可定期或延时执行的线程池

newSingleThreadExecutor的问题

使用newFixedThreadPool工厂方法创建“固定数量的线程池”的源码如下

public static ExecutorService newFixedThreadPool(int nThreads) {
 return new ThreadPoolExecutor(
 nThreads, // 核心线程数
 nThreads, // 最大线程数
 0L, // 线程最大空闲(Idle)时长
 TimeUnit.MILLISECONDS, // 时间单位:毫秒
 new LinkedBlockingQueue<Runnable>() //任务的排队队列,无界队列
 );
 }

newFixedThreadPool工厂方法返回一个ThreadPoolExecutor实例,该线程池实例的corePoolSize数量为参数nThread,其maximumPoolSize数量也为参数nThread,其workQueue属性的值为LinkedBlockingQueue()无界阻塞队列。

使用Executors创建“固定数量的线程池”的潜在问题主要存在于其workQueue上,其值为LinkedBlockingQueue(无界阻塞队列)。如果任务提交速度持续大于任务处理速度,就会造成队列中大量的任务等待。

如果队列很大,很有可能导致JVM出现OOM(Out Of Memory)异常,即内存资源耗尽。

newSingleThreadExecutor的问题

先看源码:

public static ExecutorService newSingleThreadExecutor() {
 return new FinalizableDelegatedExecutorService
 (new ThreadPoolExecutor(
 1, // 核心线程数
 1, // 最大线程数
 0L, // 线程最大空闲(Idle)时长
 TimeUnit.MILLISECONDS, //时间单位:毫秒
 new LinkedBlockingQueue<Runnable>() //无界队列
 ));
 }

FinalizableDelegatedExecutorService对该“固定大小的线程池”进行包装,这一层包装的作用是防止线程池的corePoolSize被动态地修改。

使用Executors创建的“单线程化线程池”与“固定大小的线程池”一样,其潜在问题仍然存在于其workQueue属性上,该属性的值为LinkedBlockingQueue(无界阻塞队列)。

如果任务提交速度持续大于任务处理速度,就会造成队列大量阻塞。如果队列很大,很有可能导致JVM的OOM异常,甚至造成内存资源耗尽。

newCachedThreadPool的问题

还是先看源码:

public static ExecutorService newCachedThreadPool(){
 return new ThreadPoolExecutor(
 0, // 核心线程数
 Integer.MAX_VALUE, // 最大线程数
 60L, // 线程最大空闲(Idle)时长
 TimeUnit.MILLISECONDS, // 时间单位:毫秒
 new SynchronousQueue<Runnable>() // 任务的排队队列,无界队列
 );
 }

以上代码通过调用ThreadPoolExecutor标准构造器创建一个核心线程数为0、最大线程数不设限制的线程池。

所以,理论上“可缓存线程池”可以拥有无数个工作线程,即线程数量几乎无限制。

“可缓存线程池”的workQueue为SynchronousQueue同步队列,这个队列类似于一个接力棒,入队出队必须同时传递,正因为“可缓存线程池”可以无限制地创建线程,不会有任务等待,所以才使用SynchronousQueue。

使用Executors创建的“可缓存线程池”的潜在问题存在于其最大线程数量不设限上。

由于其maximumPoolSize的值为Integer.MAX_VALUE(非常大),可以认为可以无限创建线程,如果任务提交较多,就会造成大量的线程被启动,很有可能造成OOM异常,甚至导致CPU线程资源耗尽。

newScheduledThreadPool的问题

“可调度线程池”的源码如下:

public static ScheduledExecutorService newScheduledThreadPool(int  corePoolSize) {
 return new ScheduledThreadPoolExecutor(corePoolSize);
 }

newScheduledThreadPool工厂方法调用了ScheduledThreadPoolExecutor实现类的构造器,而

ScheduledThreadPoolExecutor继承了ThreadPoolExecutor的普通线程池类,在其构造器内部进一步调用了该父类的构造器,具体的代码如下:

public ScheduledThreadPoolExecutor(int corePoolSize) {
 super(corePoolSize, // 核心线程数
 Integer.MAX_VALUE,  // 最大线程数
 0, //线程最大空闲(Idle)时长
 NANOSECONDS,//时间单位
 new DelayedWorkQueue() //任务的排队队列
 );
 }

的值为Integer.MAX_VALUE(非常大),可以认为可以无限创建线程,如果任务提交较多,就会造成大量的线程被启动,很有可能造成OOM异常,甚至导致CPU线程资源耗尽。

4.使用Executors创建“可调度线程池”的潜在问题

“可调度线程池”的源码如下

public static ScheduledExecutorService
newScheduledThreadPool(
 int corePoolSize)
 {
 return new
ScheduledThreadPoolExecutor(corePoolSize);
 }

newScheduledThreadPool工厂方法调用了ScheduledThreadPoolExecutor实现类的构造器,而

ScheduledThreadPoolExecutor继承了ThreadPoolExecutor的普通线程池类,在其构造器内部进一步调用了该父类的构造器,具体的代码如下:

public ScheduledThreadPoolExecutor(int corePoolSize) {
 super(corePoolSize, // 核心线程数
 Integer.MAX_VALUE, // 最大线程数
 0, //线程最大空闲(Idle)时长
 NANOSECONDS,//时间单位
 new DelayedWorkQueue() //任务的排队队列
 );
 }

使用Executors创建的“可缓存线程池”的潜在问题存在于其最大线程数量不设限上。由于其线程数量不设限,如果到期任务太多,就会导致CPU的线程资源耗尽。

小小的总结

以上内容分别梳理了Executors四个工厂方法所创建的线程池将面临的潜在问题。总结起来,使用Executors创建线程池主要的弊端如下:

方法 问题
FixedThreadPool和SingleThreadPool 这两个工厂方法所创建的线程池,工作队列(任务排队的队列)的长度都为Integer.MAX_VALUE,可能会堆积大量的任务,从而导致OOM(即耗尽内存资源)。
CachedThreadPool和ScheduledThreadPool 这两个工厂方法所创建的线程池允许创建的线程数量为Integer.MAX_VALUE,可能会导致创建大量的线程,从而导致OOM

所以,大厂的编程规范都不允许使用Executors创建线程池,而是要求使用标准构造器ThreadPoolExecutor创建线程池。

你可能感兴趣的:(Java,博文,多线程,executors,线程池,java)