在上篇博客中,我尝试通过一个简单的Demo介绍了一下线程池的构造参数和工作过程,这一篇博客则会继续探讨一下在使用线程池过程中可能遇到的问题。
1.线程池使用时需要遵守的规范
在阿里的Java的开发手册中对于线程池的使用用如下几条规范
- 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
- 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
- 【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这 样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
- 说明:Executors返回的线程池对象的弊端如下: 1) FixedThreadPool和SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2) CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
关于规范的前两点很好理解,接下来我们着重来看一下第三点。
- 使用newFixedThreadPool()构建线程池
@GetMapping("/oom1")
public void oom1() throws InterruptedException {
ThreadPoolExecutor threadPool = ((ThreadPoolExecutor) Executors.newFixedThreadPool(1));
printfStatus(threadPool);
for (int i = 0; i < 100000000; i++) {
threadPool.execute(() -> {
String payload = IntStream.rangeClosed(1, 1000000)
.mapToObj(__ -> "a")
.collect(Collectors.joining("")) + UUID.randomUUID().toString();
try {
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException e) {
log.info(payload);
}
});
}
threadPool.shutdown();
threadPool.awaitTermination(1, TimeUnit.HOURS);
}
private void printfStatus(ThreadPoolExecutor threadPool) {
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
log.info("======================");
log.info("Pool Size: {}", threadPool.getPoolSize());
log.info("Active Threads:{}", threadPool.getActiveCount());
log.info("Number of Tasks Completed:{}", threadPool.getCompletedTaskCount());
log.info("Number of Task in Queue:{}", threadPool.getQueue().size());
log.info("=====================");
}, 0, 1, TimeUnit.SECONDS);
}
运行结果
Exception in thread "http-nio-8080-exec-1" Exception in thread "http-nio-8080-exec-2" java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: Java heap space
很显然在我们对Executors.newFixedThreadPool(1))创建的单线程的线程池传入大量任务时出现了OutOfMemoryError,至于为什么会出现这个问题我们可以看一下newFixedThreadPool的源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
在构建FixedThreadPool时,任务队列采用的是LinkedBlockingQueue,而LinkedBlockingQueue是一个Inter.MAX_VALUE长度的队列,由于超过核心线程数的任务会被保存在任务队列之中,因此在传入大量任务后会导致大量任务堆积在LinkedBlockingQueue的任务队列之中,从而导致OOM的产生。