问题描述
在线上发生的一次问题,在场景中有这样一个业务,需要异步执行一个主任务,主任务中又包含着n个子任务,为了整个主任务能够快速处理,又将子任务按照数量获取线程资源异步处理,即异步线程a中再异步调用a1,a2,a3. a可能同时存在多个.
实际场景中,由于系统线程池分配数量较小,且一段时间内先后启动了多个主任务,耗时的主任务中又用子任务取申请线程导致线程池资源耗尽
问题原因
1. 主任务是从线程池中获取的线程资源,同时主任务比较耗时
2. 每个主任务中包含的n的子任务,会再申请线程,处理完毕释放回线程池
3. 启动了多个主任务时,每个主任务在未结束之前,都会占用自身一个线程不会释放,消耗一个线程池资源
4. 后期频繁启动主任务,可能使数量=线程池线程数,此时子任务无法再从线程池获得资源,就进入队列等待
5. 最终结果就造成了每个主任务都占用线程,但主任务内的子任务无法获取线程,线程池瘫痪不可用
问题复现
package test; import lombok.extern.slf4j.slf4j; import org.apache.commons.lang3.randomutils; import org.springframework.scheduling.concurrent.threadpooltaskexecutor; import java.util.concurrent.countdownlatch; import java.util.concurrent.threadpoolexecutor; import java.util.concurrent.timeunit; /** * @title 线程池异步线程中再次获取线程池资源的问题 * @author xingbz * @description * 记; * * 究其原因在于: * * @createdate 2020-7-17 */ @slf4j public class testwork { private static final threadpooltaskexecutor executor; static { executor = myexecutor(); } /** 初始化线程池 */ public static threadpooltaskexecutor myexecutor() { threadpooltaskexecutor executor = new threadpooltaskexecutor(); // 核心线程数 executor.setcorepoolsize(5); // 最大线程数 executor.setmaxpoolsize(20); // 排队任务队列 executor.setqueuecapacity(100); // 线程名称前缀 executor.setthreadnameprefix("异步线程-"); // 队列满后拒绝策略 executor.setrejectedexecutionhandler(new threadpoolexecutor.callerrunspolicy()); // 线程最大回收时间 executor.setkeepaliveseconds(100); // 初始化线程 executor.initialize(); return executor; } /** 模拟测试 */ public static void main(string[] args) throws exception { // 主任务数量 int mainjobnum = 20; countdownlatch maindownlatch = new countdownlatch(mainjobnum); for (int i = 0; i < mainjobnum; i++) { // 主任务编号, 方便区分 int index = i + 1; // 模拟每1秒开始一个主任务 timeunit.seconds.sleep(1); executor.submit(() -> { try { log.debug("\t执行主任务" + index); // 每个主任务随机包含n个子任务, 再异步调用线程池资源处理 int subjobnum = randomutils.nextint(2, 3); subjobworkasync(subjobnum, index); } finally { maindownlatch.countdown(); } }); } maindownlatch.await(); executor.shutdown(); log.info("完成所有任务 > > >"); } /** 异步执行子任务 */ private static void subjobworkasync(int subjobnum, int index) { countdownlatch subdownlatch = new countdownlatch(subjobnum); for (int j = 0; j < subjobnum; j++) { executor.submit(() -> { try { log.warn("\t\t\t执行一个" + index + "的子任务"); // 每个子任务模拟耗时 timeunit.seconds.sleep(3); } catch (interruptedexception e) { e.printstacktrace(); } finally { subdownlatch.countdown(); } }); } try { subdownlatch.await(); } catch (interruptedexception e) { e.printstacktrace(); } } }
执行代码,结果如下:
可以看到,线程池很快就被主任务耗尽, 导致子任务无法执行.
解决方案
1. 异步线程中不能再获取异步线程
既然主方法是异步执行了,那么其中的子任务也相对不那么要求时间.此处是我为了业务给另外一个业务复用导致了线程再调线程
2. 如果异步中确实需要再获取异步线程,需要使用新的线程池. 不能再使用自身的线程池
这是当前我们的解决方案,在系统中又单独构建了一个线程池负责子任务的业务
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论