前言
ThreadPoolExecutor的用法网上一搜一大堆, 但是对于ctrl + C/V的你来说你真的知道应该如何使用吗?
下面给大家分享一下我遇到到由于ThreadPoolExecutor使用不当造成的服务器宕机事件!
希望大家可以引以为鉴,做一个对技术保持敬畏之心的人!
事件重演
由于业务需要,需要定时对数据中的数据进行处理.所以想使用线程池提升执行任务的速度.
具体代码如下:
@Scheduled(cron = "${cron.transfer}")
public void transfer(){
// ...
// ...
// 模拟从数据库中取出需要刷新的数据源
List<Department> departmentList = new ArrayList<>();
int size = departmentList.size();
if (departmentList.isEmpty()){
return;
}
long keepAliveTime = 1;
ThreadPoolExecutor executor = new ThreadPoolExecutor(
size, size, keepAliveTime, TimeUnit.SECONDS, new ArrayBlockingQueue<>(size));
// 预启动所有核心线程
for (Department department: departmentList){
executor.execute(() -> doTransfer(department));
}
}
private void doTransfer(Department department) {
// 处理业务
// ...
// ...
}
上述代码运行几天后服务器就莫名的崩溃了.
于是我先是对服务进行恢复.
然后使用jstack和jmap命令对JVM线程和堆进行分析.
结果发现内存中有大量的线程池持有的线程未释放.
所以初步断定是由于线程池未释放造成的内存泄漏问题.
问题验证
自己在本地模拟代码测试如下:
public class TestThreadPoolExecutor {
public static void main(String[] args) throws InterruptedException {
while (true){
doTask();
// 每隔1s中执行一次任务
Thread.sleep(1000);
}
}
private static void doTask(){
int keepAliveTime = 1;
int size = 1;
ThreadPoolExecutor executor = new ThreadPoolExecutor(size, size, keepAliveTime, TimeUnit.SECONDS, new ArrayBlockingQueue<>(size));
for (int i = 0; i < size; i++){
executor.submit(() -> {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
System.out.println("================");
});
}
}
}
测试结果如下
线程截图
监视视图
线程dump截图
从上面我们可以验证出ThreadPoolExecutor虽然在方法中创建,方法执行完出栈之后,线程池并不会被销毁.因为任务执行完之后线程会阻塞在获取任务的地方.
解决方案
线程池只初始化一次.后续直接使用!
public class ThreadPool {
private static final int coreSize = 20;
private static final int maxSize = 50;
private static final int keepAliveTime = 10;
private static final ThreadPoolExecutor executor;
static {
executor = new ThreadPoolExecutor(
coreSize, maxSize, keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
executor.prestartAllCoreThreads();
}
public static void submitTask(Runnable runnable){
executor.submit(runnable);
}
}
@Scheduled(cron = "${cron.transfer}")
public void transfer(){
// ...
// ...
// 模拟从数据库中取出需要刷新的数据源
List<Department> departmentList = new ArrayList<>();
int size = departmentList.size();
if (departmentList.isEmpty()){
return;
}
for (Department department: departmentList){
ThreadPool.submit(() -> doTransfer(department));
}
}
private void doTransfer(Department department) {
// 处理业务
// ...
// ...
}