业务场景:开发票任务,遍历几十万广告账户,属于长耗时批量任务。原有共用全局线程池,开票任务执行时大量线程被长期占用,挤压定时任务、消息消费等其他业务任务,引发线程耗尽、任务堆积、线上报警。
解决思路
ConcurrentHashMap作为资源容器装各个部分的线程池,其中key为任务标识、value为对应独立线程池,实现开票任务和其他任务所使用的线程池独立。
|
区分:
|
代码实现
依赖:Java SpringBoot 环境即可
Ⅰ.线程池管理器Java类:
public class BusinessThreadPoolManager {
//新建ConcurrentHashMap来装
private final Map<String, ThreadPoolExecutor> businessThreadPoolMap = new ConcurrentHashMap<>();
//原生线程池核心实现类:根据业务写参数
//这里代码只传了 3 个核心参数(String businessKey是额外的标识),其他四个参数全用默认值
public ThreadPoolExecutor getOrCreateBusinessPool(
String businessKey,
int corePoolSize,
int maximumPoolSize,
int queueCapacity
){
return …//线程池内部看下面代码
}
}
1.提交任务到对应线程池:
2.销毁指定业务线程池,释放资源
3.销毁全部业务线程池(服务关闭时调用)
线程池内部行为代码:
return bizThreadPoolMap.computeIfAbsent( businessKey, k -> {
// 自定义线程工厂,方便日志定位开票任务线程
ThreadFactory threadFactory = new ThreadFactory() {
private final AtomicInteger num = new AtomicInteger(1); //即初始值为1
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "business-" + businessKey + "-thread-" + num.getAndIncrement());
t.setDaemon(false);
return t;
}
};
//直接返回最终确定好的线程池即可
return new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
IDLE_TIMEOUT_SECONDS, //已提前定义好为300,文章中未提及
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(queueCapacity),
threadFactory,
new ThreadPoolExecutor.CallerRunsPolicy()
);
});
其中
1.computeIfAbsent:如果 Map 中 key 不存在或者value 为 null ,则执行 mappingFunction 生成 value,放入 map,返回新 value;如果 key 已经存在,直接返回已存在的 value,不会执行 mappingFunction
2.AtomicInteger:原子整型,用来在多线程环境下安全地做整数读写、自增,不需要 synchronized 加锁。 底层基于 CAS(Compare-And-Swap,比较并交换),属于无锁并发工具
3.Runnable(接口):
区分:
- Runnable:任务(干什么事)
- Thread:执行任务的工人(线程)

4.setDaemon(false) :
- 守护线程(daemon=true):后台线程。当所有非守护线程全部结束,JVM 直接退出,守护线程会被直接终止,不等它跑完。典型例子:GC 线程。
- 用户线程 / 非守护线程(daemon=false,默认值):前台业务线程。JVM 必须等所有用户线程执行完毕,JVM 才会正常退出。
5.CallerRunsPolicy():天然限流:提交任务的线程被卡住,提交速度被迫降下来,防止疯狂往线程池压任务

Ⅱ.开票任务服务类 InvoiceTaskService.java
几十万广告账户遍历开票任务,提交到独立开票线程池,不再使用全局池。
public void batchGenerateInvoice(List<Long> adAccountIdList) {
// ========== 动态获取开票专属线程池参数 ==========
// 可配置在yml文件,根据账户数量动态调整,这里示例固定值
int core = 4;
int max = 8;
int queueCap = 200;
ThreadPoolExecutor invoicePool = bizThreadPoolManager.getOrCreateBizPool(INVOICE_BIZ_KEY, core, max, queueCap);
// 遍历几十万广告账户,拆分任务提交到开票独立线程池
for (Long accountId : adAccountIdList) {
Runnable invoiceSubTask = () -> {
try {
generateSingleAccountInvoice(accountId);
} catch (Exception e) {
// 异常埋点、告警上报
e.printStackTrace();
}
};
invoicePool.submit(invoiceSubTask);
}
}
总结:这里的getOrCreateBusinessPool看似还是普通线程池工具,但其实:




