ThreadPoolExecutor注意事项

前言

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) {
    // 处理业务
    // ...
    // ...

}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值