keepAliveTime = 0 且 corePoolSize = 0 时 ThreadPoolExecutor 的行为

Behavior of ThreadPoolExecutor with keepAliveTime = 0 and corePoolSize = 0

ThreadPoolExecutorkeepAliveTimecorePoolSize 设置为 0 是否会为每个任务创建一个新的 Thread?是否保证 Thread 不会被任何任务重复使用?

顺便说一句,我想将 maximumPoolSize 设置为 100 左右。我买不起无限数量的线程。如果我达到线程的限制(例如 100),我希望服务器回退到 'sychronous' 模式(无并行性)。见 ThreadPoolExecutor.CallerRunsPolicy.


背景(如果您对我的动机感兴趣,请阅读):

我们有一个项目依赖于 ThreadLocals 的使用(例如,我们使用 Spring 及其 SecurityContextHolder)。我们想并行调用 10 个后端系统。我们喜欢 ThreadPoolExecutor.CallerRunsPolicy,它在线程池及其任务队列已满的情况下在调用者线程中运行可调用对象。这就是为什么我们要使用 ThreadPoolExecutor。我无法将项目更改为不使用ThreadLocals,请不要建议这样做。

我在想如何以最少的工作量完成它。 SecurityContextHolder 可以切换为使用 InheritableThreadLocal 而不是 ThreadLocal。当创建子线程时,线程局部变量随后被传递给子线程。唯一的问题是如何让 ThreadPoolExecutor 为每个任务创建新的 Thread。将其 keepAliveTimecorePoolSize 设置为 0 会起作用吗?我确定 none 的线程将被重新用于下一个任务吗?我可以忍受创建新 Threads 对性能造成的影响,因为每个并行任务都需要更多时间。

考虑的其他可能解决方案:

  1. 扩展 ThreadPoolExecutorexecute 方法并将 Runnable command 参数包装到一个不同的 Runnable 中,它会记住线程局部变量到它的最终字段中,然后在中初始化它们它的 run 方法在调用目标 command 之前。我认为这可能有效,但与我最初的问题所涉及的解决方案相比,需要维护的代码略多。
  2. 在异步方法的参数中传递局部线程。这使用起来更冗长。人们也可能忘记这样做,并且安全上下文将在异步任务之间共享:-(
  3. 扩展 ThreadPoolExecutorbeforeExecuteafterExecute 方法并使用反射复制线程局部变量。这需要大约 50 行丑陋的反射代码,我不确定它有多安全。

不,这行不通! ThreadPoolExecutor 将您的 Callable/Runnable 包装到内部 Worker 对象中,并在其 runWorker() 方法中执行它。此方法的 Javadoc 说:

Main worker run loop. Repeatedly gets tasks from queue and executes them, while coping with a number of issues: ...

你也可以看一下这个方法的代码,它直到任务队列为空(或者发生一些坏事导致线程退出)才会退出。

因此将 keepAliveTime 设置为 0 不一定会在每个提交的任务上产生一个新线程。

您可能应该使用解决方案 3,因为 beforeExecute()afterExecute() 方法正是用于处理 ThreadLocals。

或者,如果您坚持为每个任务创建新线程,您可以查看 Spring 的 SimpleAsyncTaskExecutor。它保证每个任务都有一个新线程,并允许您设置并发限制,即相当于 ThreadPoolExecutor#maxPoolSize.