最近在看阿里Java编程规范,有一条引起了我的注意:
【强制】线程池不允许使用
Executors
去创建,而是通过ThreadPoolExecutor
的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors
返回的线程池对象的弊端如下:
FixedThreadPool
和SingleThreadPool
: 允许的请求队列长度为Integer.MAX_VALUE
,可能会堆积大量的请求,从而导致 OOM。CachedThreadPool
和ScheduledThreadPool
: 允许的创建线程数量为Integer.MAX_VALUE
,可能会创建大量的线程,从而导致 OOM。
平时没怎么注意这一点,一般用的多的还是FixedThreadPool
和 SingleThreadPool
。那怎么理解这条规范呢?首先,ThreadPoolExecutor
是更底层的类,直接用它的构造方法,可以让我们对创建的线程池有更强的控制。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
我们来看下ThreadPoolExecutor
的参数设定:
corePoolSize
:核心线程数(就算有空闲线程,也不会低于这个数)maximumPoolSize
:最大线程数keepAliveTime
:最大存活时间(超过corePoolSize
数量的线程空闲下最大存活时间)unit
:时间单位workQueue
:工作队列(核心线程满后,任务会进入这里)handler
:任务满负荷(超出最大线程数和工作队列上限之和)后的处理策略
其中处理策略有四种:
AbortPolicy
:默认策略,在需要拒绝任务时抛出RejectedExecutionException
CallerRunsPolicy
:直接在 execute 方法的调用线程中运行被拒绝的任务,如果线程池已经关闭,任务将被丢弃DiscardPolicy
:直接丢弃任务DiscardOldestPolicy
:丢弃队列中等待时间最长的任务,并执行当前提交的任务,如果线程池已经关闭,任务将被丢弃
除此之外我们也可以自定义处理策略。
作为对比,我们看下FixedThreadPool
的实现:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
可见它实际上就是个简化版的ThreadPoolExecutor
,通过固定线程数,然后指定工作队列长度为无限(不传长度代表容量无限)。这样确实可能会造成任务无限堆积,引起OOM的问题。所以阿里的规范建议我们直接使用更底层的ThreadPoolExecutor
,把处理策略之类的都提前设定好。
不过规范也不是死的,在实际情况中也无法避免使用FixedThreadPool
等更高层的实现。一方面是因为使用起来更加方便,另一方面也是有时候使用ThreadPoolExecutor
并没有明显的好处。例如:线程池中分配的任务都是关键任务,无法被丢弃,然后因为性能考虑也无法被execute线程同步执行,这样就很难定义一个合适的处理策略,不如直接使用FixedThreadPool
。就算有OOM的风险,那也只能说在多种风险权衡下做的一个评估和取舍。