为什么阿里禁止通过Executors创建线程池

Executors是通过new一个ThreadPoolExecutor来创建的线程池。来看看ThreadPoolExecutor的构造方法:

1
2
3
4
5
6
7
8
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}

方法上的Javadoc:

1
2
3
4
5
6
7
8
9
10
11
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.

解释一下:
corePoolSize :保持在线程池中的线程,哪怕这几个线程是空闲的。除非设置了allowCoreThreadTimeOut。
maximumPoolSize:线程池中允许的最大的线程数量。
keepAliveTime:多余corePoolSize 设置的线程的存活时间(比如corePoolSize 3,maximumPoolSize 10,那么剩下的7个线程将只能存活keepAliveTime)。
unit :keepAliveTime的时间单位
workQueue:保存corePoolSize 线程处理不了的任务(比如corePoolSize 3,但是有10个任务要处理,那么剩下的7个任务将存储在workQueue中)。当此队列满了之后,会再次创建不超过maximumPoolSize大小的线程来处理。

好,看完了ThreadPoolExecutor构造函数,再来看看Executors是怎么设置这些参数创建线程池的,以及会带来哪些问题。

看看Executors创建线程池的源码:

newFixedThreadPool

1
2
3
4
5
 public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

corePoolSize、maximumPoolSize 设置为相同(nThreads)大小,也就是说这个线程池内部会一直存在nThreads数量的线程。并且由于corePoolSize和maximumPoolSize 相同,keepAliveTime也就无意义了,所以设置为0L。
这个线程池使用的是LinkedBlockingQueue(无界队列)来存corePoolSize线程无法处理的任务。
那么这个线程池问题在哪里呢?
最大的问题在于workQueue使用了无界队列,当任务数多到线程池处理不过来时,任务全部进入workQueue,会消耗很大的内存,甚至OOM。

newSingleThreadExecutor

1
2
3
4
5
6
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

可以发现newSingleThreadExecutor 是 newFixedThreadPool的一个具化版本。指定了固定的nThreads:1。所以这个线程池的问题跟newFixedThreadPool一样。当任务过多时,可能出现OOM。

newCachedThreadPool

1
2
3
4
5
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

corePoolSize 设置为0,也就是说默认这个线程池中不会有线程创建。 使用的workQueue是SynchronousQueue,只有被消费才能继续生产。此时来了一个任务,发现没有线程来消费,于是创建一个线程来消费。并且这个线程在完成任务后最多存活60秒。如果此时来了很多任务。那么这个线程最大可创建Integer.MAX_VALUE个线程。这就是这个线程池的问题所在了,线程数量可以基本上说是无限制,可能导致资源耗尽。


Executors中还有很多创建线程池的工厂方法,或多或少都存在上述问题。通过上面的问题可以发现,要创建一个可靠的线程池,最好还是手动创建,并且合理指定corePoolSize、maximumPoolSize 、workQueue等参数。

例如:
为了避免OOM,我们可以使用有界队列ArrayBlockingQueue来替代无界队列。
为了避免线程过多资源耗尽,我们需要结合实际情况来指定一个maximumPoolSize,而不是粗暴的设置为Integer.MAX_VALUE。

1
2
3
4
5
6
7
8
new ThreadPoolExecutor(
2,
8,
30L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000),
Executors.defaultThreadFactory(),
new AbortPolicy());

上面这个线程池,当添加进任务时,默认创建2个线程来处理任务。如果又来了一个任务,此时会进入ArrayBlockingQueue(workQueue)排队,当workQueue满了(1000个任务)会在创建6个线程来处理任务。如果此时还有任务添加进来,则会执行AbortPolicy策略,默认是拒绝添加。当所有任务处理完成后,这6个线程在空闲30秒后将会被销毁。

实际使用时应该结合实际情况调整,并且自定义ThreadFactory以达到设置线程名称的目的。

原创,如有雷同纯属巧合。

为什么阿里禁止通过Executors创建线程池

https://jingzhouzhao.github.io/archives/3223999.html

作者

太阳当空赵先生

发布于

2019-06-28

更新于

2022-02-22

许可协议

评论