小组群内leader甩出来一个截图,说是线程有些异常,一直在增长,从pool-1-thread一直到pool-285-thread,每个pool都只有三个线程:thread-1 thread-2 thread-3,第一反应感觉肯定和定时任务有关,这种稳定的有规律的一般都是和定时任务有关。
定位线程是哪部分的代码
如果线程池自定义名字了,直接在项目里搜就可以
直接查询最新的线程的日志,比如这里就是查询pool-285-thread对应的日志,如果有日志的话,很大概率就能直接定位到是哪里的问题,很不幸,我们出问题的这部分当时没有打印日志
通过jstack分析
分析代码
task(){
ExecutorService executorService = Executors.newFixedThreadPool(6);
executorService.execute(()->{统计1})
executorService.execute(()->{统计2})
executorService.execute(()->{统计3})
}
线上出问题的代码大概是上面的逻辑,根据现象我们知道,一共开启了285个线程池,线程池每次有三个线程在工作。
为什么会开启这么多线程池?
由于Executors.newFixedThreadPool(6)是放在类里面,且没有手动shutdown,导致每次定时任务执行这个方法的时候都会开启一个线程池
为什么线程池里的线程没有回收?
newFixedThreadPool最大线程和核心线程数量是一样的,即这里创建了6个核心线程,而核心线程的回收又得手动指定allowCoreThreadTimeOut为true才可以,所以并不会被回收
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 复现验证
public class Case {
void fixPoolInc() {
ExecutorService executorService = Executors.newFixedThreadPool(6);
for (int i = 0; i < 3; i++) {
executorService.execute(() -> {
System.out.println("ThreadName:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
@SneakyThrows
public static void main(String[] args) {
Case aCase = new Case();
for (int i = 0; i < 10; i++) {
Thread.sleep(5000);
aCase.fixPoolInc();
}
}
}
开10个线程池,每个线程池启动3个线程,通过jconsole观察是否线程持续增长,并没有回收
结论
方法内部开启的线程池记得手动关闭,或者将方法内部的线程池提到外部
最好还是通过ThreadPoolExecutor手动设置每个参数,这样对于业务更清晰,而不是偷懒使用ExecutorService
线程池最好还是指定明细,这边方便排查异常
参考文档:
Java线程Dump分析_线程dump怎么分析_萨达哈鲁君的博客-CSDN博客