最近在看阿里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:默认策略,在需要拒绝任务时抛出RejectedExecutionExceptionCallerRunsPolicy:直接在 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的风险,那也只能说在多种风险权衡下做的一个评估和取舍。

探讨阿里Java编程规范中关于线程池的使用建议,分析Executors与ThreadPoolExecutor的区别,解释FixedThreadPool潜在的OOM风险及如何通过设置合理的处理策略来规避。
9280

被折叠的 条评论
为什么被折叠?



