踩坑之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包里的,我们看一下这个类的类图:

image-20210508164525656

发现他也是实现的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.

这里很清楚的说明了,这个类并不会重用线程,设计初衷是为了执行大量的短时间的任务;

最后,只想说,且用且谨慎呀!!!

posted @   faylinn  阅读(7474)  评论(1编辑  收藏  举报
编辑推荐:
· 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语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
、、、
点击右上角即可分享
微信分享提示