在线上发生的一次问题, 在场景中有这样一个业务, 需要异步执行一个主任务, 主任务中又包含着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. 如果异步中确实需要再获取异步线程, 需要使用新的线程池. 不能再使用自身的线程池
这是当前我们的解决方案, 在系统中又单独构建了一个线程池负责子任务的业务