首先了解下Executors的方法创建出来的5种常用线程池
名称 | 说明 |
---|---|
newCachedThreadPool | 可缓存的线程池 |
newFixedThreadPool | 固定大小的线程池 |
newSingleThreadExecutor | 固定单个线程的线程池 |
newScheduledThreadPool | 用作任务调度的线程池 |
newWorkStealingPool | 足够大小的线程池,JDK1.8新增的 |
看下每种线程池都是怎么创建的
- newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可以看到它允许创建的最大线程数是Integer.MAX_VALUE,使用的任务队列是SynchronousQueue,是个用于线程同步的阻塞队列,它没有实际的容量。所以如果直接创建这个线程池,很可能会创建大量线程,导致OOM。
- newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到它的核心线程数和最大线程数是一样的,由使用者来决定具体值,使用的任务队列是LinkedBlockingQueue,是个有界阻塞队列,如下源码,可知这个队列的最大长度是Integer.MAX_VALUE,这样可能会导致大量的请求堆积,直接OOM。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
- newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看到它的核心线程数和最大线程数是一样的,固定是1,使用的任务队列是LinkedBlockingQueue,是个有界阻塞队列,这个队列的最大长度是Integer.MAX_VALUE,这样可能会导致大量的请求堆积在这个队列中,导致出现OOM。
- newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
可以看到它允许创建的最大线程数是Integer.MAX_VALUE,使用的队列是DelayedWorkQueue,是个支持延迟操作的无界阻塞队列。所以使用这个线程池,如果有大量线程被创建,就会导致OOM。
- newWorkStealingPool
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
public ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
boolean asyncMode) {
this(checkParallelism(parallelism),
checkFactory(factory),
handler,
asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
"ForkJoinPool-" + nextPoolId() + "-worker-");
checkPermission();
}
这个线程池它的实现和上面4种都不一样,用的是ForkJoinPool类。这是个并行的线程池,传入的参数是并发线程数,它里面的任务执行是无序的,哪个线程抢到任务就哪个线程执行,ForkJoinPool主要用到的是双端队列。
总结
从以上对各个类型的线程池的分析可以发现,除了newWorkStealingPool用的是ForkJoinPool类,其他线程池的构建最后其实都调用了ThreadPoolExecutor类的构造方法去创建线程池。而这些类型的线程池只是根据自己的需要来传入了一些默认的参数,也正是因为这些参数,才会导致可能出现OOM的问题。那么看下使用ThreadPoolExecutor来创建线程池的示例代码:
ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(1));
可以看到使用ThreadPoolExecutor来创建线程池,里面所有的参数我们都可以根据需求来自己指定,这样使用起来就放心了许多。所以推荐使用ThreadPoolExecutor来创建线程池。