常见的线程池创建方式。
创建线程池的方式主要分为两种,自动创建和手动创建:
a、通过 Executors 执行器自动创建线程池。
b、通过 ThreadPoolExecutor 手动创建线程池。
Executors:
Executors创建线程池很便捷,常用的创建方式有6种:
1、newSingleThreadExecutor() 创建一个单线程化的线程池;
2、newFixedThreadPool() 创建固定线程数量的线程池;
3、newCachedThreadPool() 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
4、newScheduledThreadPool() 创建一个可以延迟或定期执行任务的线程池;
5、newSingleThreadScheduledExecutor()创建一个单线程执行器,该执行器可以安排命令在给定延迟后运行,或定期执行;
6、newWorkStealingPool() 使用可用处理器数量作为其目标并行度级别创建工作窃取线程池 [ JDK1.8新增]。
Executors提供了这么多创建方法,功能还是很强大的,又方便又简洁,我们直接用不就好了吗!但通常不建议使用这个类来创建线程池,因为很有可能造成资源耗尽的风险。
OOM源头所在!
既然我们知道Executors创建的线程池存在OOM的风险,那么我们深入Executors的源码来分析一下到底是什么原因导致的呢?
以newFixedThreadPool为例:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可发现,创建线程池所用的阻塞队列是LinkedBlockingQueue。而再看看LinkedBlockingQueue的构造函数,我们能看到默认构造出来的是一个容量为Integer.MAX_VALUE的队列。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
所以当大量的线程在创建的时候,线程池处理不过来,就会加入到等待队列中,而默认创建的等待队列是一个容量为Integer.MAX_VALUE的无边界队列,从而就很有可能在服务器资源紧缺的情况下因为任务过多而导致内存溢出问题。
上面的问题是FixedThreadPool 和 SingleThreadPool会出现的情况;
但是CacheThreadPool和ScheduledThreadPool也存在类似问题。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
从源码可以看出这两种情况都将最大线程数设置成Integer.MAX_VALUE,同样也会造成OOM。
ThreadPoolExecutor:
上面的Executors创建线程池的方法内部都是调用ThreadPoolExecutor,只不过屏蔽了创建的参数。
所以我们应自己使用ThreadPoolExecutor来进行线程池的创建。这样才能让我们更加明确线程池的运行规则,从而规避系统资源耗尽的情况发生。
ThreadPoolExecutor构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize :线程池中核心线程数,即使线程处于空闲状态,也要保留在线程池中的线程数;
maximumPoolSize :线程池中允许的最大线程数;
keepAliveTime :当线程数大于核心线程数时,这是多余的空闲线程在终止前等待新任务的最大时间;
unit :keepAliveTime参数的时间单位;
workQueue :在任务执行之前用于保存任务的队列;
threadFactory :当执行程序创建一个新的线程处理程序时使用的工厂;
handler :拒绝策略,当执行因为线程边界和队列容量达到而阻塞时使用的处理程序。
总结:
本文给大家分析了Excutors导致OOM出现的原因,也为大家介绍了使用ThreadPoolExcutor手动创建线程池时每个参数的具体含义,希望在实际开发中根据具体情况,创建合适的线程池,避免OOM的发生。