ThreadPoolExecutor注意事项

本文分享了因ThreadPoolExecutor使用不当造成服务器宕机的事件。业务中用线程池处理数据,运行几天后服务器崩溃。经分析,是线程池未释放导致内存泄漏。本地测试验证了方法执行完线程池不会销毁。解决方案是线程池只初始化一次,后续直接使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

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

}
### 关于Python中`async`定义的函数注意事项及最佳实践 #### 函数声明与调用方式 当使用 `async def` 定义异步函数时,该函数会返回一个协程对象。要执行这个协程中的代码,必须通过事件循环来调度它运行。可以利用 `await` 表达式等待另一个协程完成,也可以把协程注册到事件循环里去执行[^1]。 ```python import asyncio async def main(): print('Hello') await asyncio.sleep(1) print('World') # 正确的方式是在事件循环中运行main()协程 asyncio.run(main()) ``` #### 避免阻塞操作 在编写异步函数内部逻辑的时候,应该尽量避免任何可能引起线程被长时间挂起的操作,比如 I/O 密集型的任务(文件读写、网络请求等),因为这会影响整个程序性能以及响应速度。对于不可避免的情况,则应当考虑将其转换成非阻塞形式或者交给专门的工作进程/线程池处理[^2]。 ```python from concurrent.futures import ThreadPoolExecutor import asyncio def blocking_io(): with open('/dev/urandom', 'rb') as f: return f.read(100) async def run_blocking_io(executor): loop = asyncio.get_running_loop() result = await loop.run_in_executor( executor, blocking_io) return result executor = ThreadPoolExecutor(max_workers=3) asyncio.run(run_blocking_io(executor)) ``` #### 错误处理机制 由于异常抛出会立即终止当前正在执行的协程及其父级任务链路,所以在设计上需特别注意错误传播路径的设计;通常建议采用 try-except 结构包裹住可能出现问题的部分,并合理设置超时限制以防止无限期等待下游服务回复而造成资源浪费[^3]。 ```python async def fetch_data(url): timeout = aiohttp.ClientTimeout(total=5) async with aiohttp.ClientSession(timeout=timeout) as session: try: async with session.get(url) as resp: if resp.status != 200: raise Exception(f'Error status code: {resp.status}') data = await resp.text() return data except TimeoutError: print("The request timed out.") except Exception as e: print(e) ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值