前言
本篇文章主要介绍 线程池使用时的相关知识。通过阅读你会有如下收获:
1. 为什么不建议使用Executors快捷的工具方法创建线程池?
2. 不同类型的任务混用同一个线程池有什么不良后果?
3. 有依赖关系的任务使用同一个线程池有什么不良后果?
4. 线程池中任务的异常应该怎么处理?
一. 为什么不建议使用Executors快捷的工具方法创建线程池?
《阿里巴巴 Java 开发手册》中提到,禁止使用这些方法来创建线程池,而应该手动 new ThreadPoolExecutor 来创建线程池。
这些工具方法中最常见的就是 newFixedThreadPool 和 newCachedThreadPool,而这两种方法创建出的线程池可能因为资源耗尽导致 OOM 问题。测试代码如下:
/**
* 测试FixedThreadPool
*/
private static void testFixedThreadPool() {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
IntStream.rangeClosed(1, 1000000).forEach(i -> fixedThreadPool.execute(() -> {
try {
Thread.sleep(1000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
}
/**
* 测试CachedThreadPool
*/
private static void testCachedThreadPool() {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
IntStream.rangeClosed(1, 1000000).forEach(i -> cachedThreadPool.execute(() -> {
try {
Thread.sleep(1000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
}
我们具体看下其对应的实现就可以知道原因。
1. newFixedThreadPool
首先来看下 newFixedThreadPool,线程池的工作队列直接 new 了一个 LinkedBlockingQueue,而默认构造方法的 LinkedBlockingQueue 是一个 Integer.MAX_VALUE 长度的队列,可以认为是无界的:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
...
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
...
}
如果任务较多并且执行较慢的情况下,会向队列中一直添加待处理任务,很快就会内存耗尽。
2. newCacheThreadPool
接下来看看newCacheThreadPool,查看其的源码可以看到,这种线程池的最大线程数是 Integer.MAX_VALUE