踩坑之SimpleAsyncTaskExecutor
今天在项目中看见了一个《线程池》的定义,使用了SimpleAsyncTaskExecutor,之前没有了解过这个,出于好奇,查阅了源码,发现了一些惊天大秘密;
这个《线程池》的Bean是这样定义的:
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("async-pool-");
executor.setConcurrencyLimit(Runtime.getRuntime().availableProcessors() + 1);
executor.setTaskDecorator(runnable -> {
SecurityContext context = SecurityContextHolder.getContext();
return () -> {
try {
SecurityContextHolder.setContext(context);
runnable.run();
} finally {
SecurityContextHolder.clearContext();
}
};
});
return executor;
看起来平平无奇,因为这个类提供了TaskDecorator修饰,可以将上下文的一些信息传递,比较方便,可能当时是因为这个原因所以选择了这个《线程池》吧。
这个类是在spring包里的,我们看一下这个类的类图:
发现他也是实现的Executor,我们再看下他的执行逻辑:
public void execute(Runnable task, long startTimeout) {
Assert.notNull(task, "Runnable must not be null");
// 在当前使用场景中,会走包装的方法;
Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
// 判断是否是被包装过
if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
//在当前场景中,一定会走这里;所以每次都会执行beforeAccess方法
this.concurrencyThrottle.beforeAccess();
// 这里和线程池完全不同,因为每次都是创建新的线程;
doExecute(new ConcurrencyThrottlingRunnable(taskToUse));
}
else {
doExecute(taskToUse);
}
}
protected void beforeAccess() {
// concurrencyLimit并发限制数量, NO_CONCURRENCY=0,如果并发限制为0则报错
if (this.concurrencyLimit == NO_CONCURRENCY) {
throw new IllegalStateException(
"Currently no invocations allowed - concurrency limit set to NO_CONCURRENCY");
}
// 并发限制>0
if (this.concurrencyLimit > 0) {
boolean debug = logger.isDebugEnabled();
// 全局锁
synchronized (this.monitor) {
boolean interrupted = false;
// 如果当时的并发数量大于并发限制数量
while (this.concurrencyCount >= this.concurrencyLimit) {
if (interrupted) {
throw new IllegalStateException("Thread was interrupted while waiting for invocation access, " +
"but concurrency limit still does not allow for entering");
}
if (debug) {
logger.debug("Concurrency count " + this.concurrencyCount +
" has reached limit " + this.concurrencyLimit + " - blocking");
}
try {
// 则等待。。一直等待。。等到并发数量变小。。
this.monitor.wait();
}
catch (InterruptedException ex) {
// Re-interrupt current thread, to allow other threads to react.
Thread.currentThread().interrupt();
interrupted = true;
}
}
if (debug) {
logger.debug("Entering throttle at concurrency count " + this.concurrencyCount);
}
// 将并发数量增加
this.concurrencyCount++;
}
}
}
//创建线程。。
protected void doExecute(Runnable task) {
Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
thread.start();
}
看了这个源码,不禁怀疑人生。回看实例化的时候配置:
executor.setConcurrencyLimit(Runtime.getRuntime().availableProcessors() + 1);
这段代码是在导出服务中用于异步处理导出任务的,也就是说如果导出的需求数量大于(当前Java虚拟机的可用的处理器数量+1),那么就会死等到前一个任务结束;然后new新的线程去处理;这样我们异步的意义是什么? 用户点击导出,然后会等到前一个任务结束,可能会导致请求超时;
这样无疑是一个坑;
起初看到这个以为只是为了TaskDecorator而使用的spring重新包装的线程池,但是仔细研究才发现这是个假的线程池,因为他每次都会new新的线程,而且无法管理线程的正常运作;
后来发现从类的注释也可以看到作者的用意:
<p><b>NOTE: This implementation does not reuse threads!</b> Consider a
thread-pooling TaskExecutor implementation instead, in particular for
executing a large number of short-lived tasks.
这里很清楚的说明了,这个类并不会重用线程,设计初衷是为了执行大量的短时间的任务;
最后,只想说,且用且谨慎呀!!!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决