作者:烧鸡太子爷
最近在看阿里云的[开发者藏经阁榜单]里发布的《泰山版Java开发手册》(我想这个很多兄弟应该都知道,阿里云的开发手册其实已经成为了很多开发者兄弟们必读的书了),里面有段关于线程的规范,而且是强制的,我们先看下说明
[强制]线程池不允许使用Executors去创建
为什么禁用?下面有说明,是因为线程数量或者队列长度设置为最大值在某些情况下可能会引起OOM,有些年轻的开发兄弟就会说了,我用Executors都没见过这么参数的啊,我们通过看看他的源码来一探究竟。
简单介绍
我们都知道Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService,通过Executor创建返回ExecutorService且常用的有三种函数(newWorkStealingPool等极少用就不做说明),分别为
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
ThreadPoolExecutor说明
先贴一下ThreadPoolExecutor源码:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
- corePoolSize => 线程池核心线程数量
- maximumPoolSize => 线程池最大数量
- keepAliveTime => 空闲线程存活时间
- unit => 时间单位
- workQueue => 线程池所使用的缓冲队列
- threadFactory => 线程池创建线程使用的工厂
- handler => 线程池对拒绝任务的处理策略
具体的大家还可以看下 https://developer.hs.net/thread/2124
看源码
上面讲了那么多,我们就来看看源码,通过源码我们应该理解会更快一些
newFixedThreadPool
Executors类里面有两个newFixedThreadPool的方法,可差异只是多了一个ThreadFactory参数,而这个参数对性能的影响有限,所以我们不多做展示,只说明第一个函数方法
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- corePoolSize = maximumPoolSize = nThreads,nThreads是入参也就意味着可以输入很大的值,有引起OOM的可能
- keepAliveTime = 0L,unit 为 毫秒
- workQueue 为 LinkedBlockingQueue,这里需要注意正是由于使用的是LinkedBlockingQueue,在资源有限的时候容易引起OOM异常
newSingleThreadExecutor
newSingleThreadExecutor也是Executors类里面的方法
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService (
new ThreadPoolExecutor(1, 1, 0L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
}
SingleThreadExecutor是单线程线程池,只有一个核心线程
- corePoolSize = maximumPoolSize = 1
- keepAliveTime = 0L,unit 为 毫秒
- workQueue 用的 LinkedBlockingQueue,那么他就会出现和newFixedThreadPool的问题,而且因为处理的线程数少,处理能力更差,更会引发OOM
newCachedThreadPool
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
- corePoolSize = 0,核心线程池的数量为0
- maximumPoolSize = Integer.MAX_VALUE,线程池最大数量为Integer.MAX_VALUE。
我们再来看看MAX_VALUE的值是多大
public static final int MAX_VALUE = 0x7fffffff;
- keepAliveTime = 60L
- unit 为 秒
- workQueue 为 SynchronousQueue
当一个任务提交时,corePoolSize为0不创建核心线程,SynchronousQueue是一个不存储元素的队列,可以理解为队里永远是满的,因此最终会创建非核心线程来执行任务。对于非核心线程空闲60s时将被回收。因为Integer.MAX_VALUE非常大,可以认为是可以无限创建线程的,在资源有限的情况下容易引起OOM异常
总结
通过上面的展示我们可以看到
- FixedThreadPool和SingleThreadExecutor 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而引起OOM异常
- CachedThreadPool允许创建的线程数为Integer.MAX_VALUE,可能会创建大量的线程,从而引起OOM异常
这就是为什么禁止使用Executors去创建线程池,而是推荐自己去创建ThreadPoolExecutor的原因
当然,Executors其实就是对ThreadPoolExecutor做了封装,所以还不如直接使用ThreadPoolExecutor