又是一个金九银十的面试季,相信很多小伙伴都会被问过创建线程有几种方式?有不少的博客都会回答有四种创建方式,分别是以下方式:
1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和Future创建线程
4)使用线程池例如用Executor框架
但是看过阿里巴巴开发规范手册的,都知道在手册里面写着 线程池不允许使用Executors去创建。
方法名 | 描述 |
---|---|
newSingleThreadExecutor() | 创建只有一个线程的线程池 |
newFixedThreadPool(int nThreads) | 创建固定大小的线程池 |
newCachedThreadPool() | 创建一个不限制数量的线程池 |
newScheduledThreadPool | 创建一个可定期或延时执行的线程池 |
使用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)异常,即内存资源耗尽。
先看源码:
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异常,甚至造成内存资源耗尽。
还是先看源码:
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线程资源耗尽。
“可调度线程池”的源码如下:
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创建线程池。