为什么阿里禁止通过Executors创建线程池
Executors是通过new一个ThreadPoolExecutor来创建的线程池。来看看ThreadPoolExecutor的构造方法:
1 | public ThreadPoolExecutor(int corePoolSize, |
方法上的Javadoc:
1 | * @param corePoolSize the number of threads to keep in the pool, even |
解释一下:
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 | public static ExecutorService newFixedThreadPool(int nThreads) { |
corePoolSize、maximumPoolSize 设置为相同(nThreads)大小,也就是说这个线程池内部会一直存在nThreads数量的线程。并且由于corePoolSize和maximumPoolSize 相同,keepAliveTime也就无意义了,所以设置为0L。
这个线程池使用的是LinkedBlockingQueue(无界队列)来存corePoolSize线程无法处理的任务。
那么这个线程池问题在哪里呢?
最大的问题在于workQueue使用了无界队列,当任务数多到线程池处理不过来时,任务全部进入workQueue,会消耗很大的内存,甚至OOM。
newSingleThreadExecutor
1 | public static ExecutorService newSingleThreadExecutor() { |
可以发现newSingleThreadExecutor 是 newFixedThreadPool的一个具化版本。指定了固定的nThreads:1。所以这个线程池的问题跟newFixedThreadPool一样。当任务过多时,可能出现OOM。
newCachedThreadPool
1 | public static ExecutorService newCachedThreadPool() { |
corePoolSize 设置为0,也就是说默认这个线程池中不会有线程创建。 使用的workQueue是SynchronousQueue,只有被消费才能继续生产。此时来了一个任务,发现没有线程来消费,于是创建一个线程来消费。并且这个线程在完成任务后最多存活60秒。如果此时来了很多任务。那么这个线程最大可创建Integer.MAX_VALUE个线程。这就是这个线程池的问题所在了,线程数量可以基本上说是无限制,可能导致资源耗尽。
Executors中还有很多创建线程池的工厂方法,或多或少都存在上述问题。通过上面的问题可以发现,要创建一个可靠的线程池,最好还是手动创建,并且合理指定corePoolSize、maximumPoolSize 、workQueue等参数。
例如:
为了避免OOM,我们可以使用有界队列ArrayBlockingQueue来替代无界队列。
为了避免线程过多资源耗尽,我们需要结合实际情况来指定一个maximumPoolSize,而不是粗暴的设置为Integer.MAX_VALUE。
1 | new ThreadPoolExecutor( |
上面这个线程池,当添加进任务时,默认创建2个线程来处理任务。如果又来了一个任务,此时会进入ArrayBlockingQueue(workQueue)排队,当workQueue满了(1000个任务)会在创建6个线程来处理任务。如果此时还有任务添加进来,则会执行AbortPolicy策略,默认是拒绝添加。当所有任务处理完成后,这6个线程在空闲30秒后将会被销毁。
实际使用时应该结合实际情况调整,并且自定义ThreadFactory以达到设置线程名称的目的。
原创,如有雷同纯属巧合。
为什么阿里禁止通过Executors创建线程池