为什么使用 ThreadPoolExecutor 的子线程不能暴露给父线程的任何继承上下文?
Why must a child thread that uses a ThreadPoolExecutor not be exposed to any inherited context of the parent thread?
我做了一个实现ApplicationListener<AuthenticationSuccessEvent>
的组件,应该记录ip地址,ip地址可以从HttpServletRequest
中检索。显然这个组件在子线程中是 运行,因此我需要在 DispatcherServlet
组件上将 属性 ThreadContextInheritable
设置为 true 才能访问 HttpServletRequest
. (我尝试使用 RequestContextListener 但没有效果)。
在 spring 文档 which can be found here 中,当您将 ThreadContextInheritable
设置为 true
时会出现以下警告。
WARNING: Do not use inheritance for child threads if you are accessing
a thread pool which is configured to potentially add new threads on
demand (e.g. a JDK ThreadPoolExecutor), since this will expose the
inherited context to such a pooled thread.
我的问题是:为什么将继承的上下文暴露给池线程是一件坏事?在这种情况下会出什么问题?另外,它们是指使用 ThreadPoolExecutor
实例的子线程还是指使用 ThreadPoolExecutor
创建的子线程?
InheritableThreadLocal 扩展了 ThreadLocal 并在我们需要在创建时将父 thread-local 属性值自动传递给子线程时使用。
当您每次都创建新的子线程而不重用已创建的线程时,这种继承工作得很好。
让我们考虑一个场景——
我们有大小为 5 的线程池。
当我们将前 5 个任务发送到线程池以处理每个任务时,都会创建新线程,因此 thread-local 属性的继承工作得很好。
问题从第 6 个请求开始。
当您发送第 6 个任务时,不会创建新线程,但会重新使用线程池中已创建的线程来处理它。
在第 6 次请求时,父 thread-local 属性值不会继承到子线程,因为我们没有创建新线程,而是重用池中已经创建的线程。
此代码片段将解释要点 -
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DeadlyComboTest {
public static void main(String[] args) throws InterruptedException {
int noOfThreads = 5;
//Execution 1
int threadPoolSize = noOfThreads;
//Execution 2 : uncomment below line and comment line above
//int threadPoolSize = noOfThreads/2;
//1. create thread pool
ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
//2. create Inheritable thread local
InheritableThreadLocal<Object> value = new InheritableThreadLocal<>();
//3. create new command and execute using thread pool created in step 1
for (int i = 1; i <= noOfThreads; i++) {
value.set(i);
executor.execute(() -> printThreadLocalValue(value));
}
executor.shutdown();
}
private static void printThreadLocalValue(ThreadLocal<Object> value) {
System.out.println(Thread.currentThread().getName() + " = " + value.get());
}
}
执行 1:noOfThreads = threadPoolSize = 5
OUTPUT: you may get the output in different sequence
pool-1-thread-1 = 1
pool-1-thread-2 = 2
pool-1-thread-3 = 3
pool-1-thread-4 = 4
pool-1-thread-5 = 5
一切看起来都很好。
根据每个请求创建新线程并正确创建子线程
继承父级的 thread-local 值。
执行 2:noOfThreads = 5 && threadPoolSize = noOfThreads/2 = 2
OUTPUT:
pool-1-thread-1 = 1
pool-1-thread-2 = 2
pool-1-thread-1 = 1
pool-1-thread-2 = 2
pool-1-thread-1 = 1
只有前两个请求 thread-local 继承工作正常,
由于线程池大小为 2,因此对于前两个请求新线程
得到创建和汇集。
第三个请求已经从池中创建的线程被重用
它仍然具有旧线程的继承值。
执行 2 是一个真实场景,池中的线程将被重用。
当我们使用线程池和 InheritableThreadLocal 组合时会出现什么问题?
这将导致意外的信息泄漏,因为池中的 worker/child 线程会将一个线程的 thread-local 属性值泄漏到另一个线程。
如果您的业务逻辑依赖于这些属性值,您将很难跟踪错误。
这就是为什么 spring 文档警告不要使用这种线程池和 InheritableThreadLocal 的组合。
我做了一个实现ApplicationListener<AuthenticationSuccessEvent>
的组件,应该记录ip地址,ip地址可以从HttpServletRequest
中检索。显然这个组件在子线程中是 运行,因此我需要在 DispatcherServlet
组件上将 属性 ThreadContextInheritable
设置为 true 才能访问 HttpServletRequest
. (我尝试使用 RequestContextListener 但没有效果)。
在 spring 文档 which can be found here 中,当您将 ThreadContextInheritable
设置为 true
时会出现以下警告。
WARNING: Do not use inheritance for child threads if you are accessing a thread pool which is configured to potentially add new threads on demand (e.g. a JDK ThreadPoolExecutor), since this will expose the inherited context to such a pooled thread.
我的问题是:为什么将继承的上下文暴露给池线程是一件坏事?在这种情况下会出什么问题?另外,它们是指使用 ThreadPoolExecutor
实例的子线程还是指使用 ThreadPoolExecutor
创建的子线程?
InheritableThreadLocal 扩展了 ThreadLocal 并在我们需要在创建时将父 thread-local 属性值自动传递给子线程时使用。
当您每次都创建新的子线程而不重用已创建的线程时,这种继承工作得很好。
让我们考虑一个场景—— 我们有大小为 5 的线程池。 当我们将前 5 个任务发送到线程池以处理每个任务时,都会创建新线程,因此 thread-local 属性的继承工作得很好。 问题从第 6 个请求开始。 当您发送第 6 个任务时,不会创建新线程,但会重新使用线程池中已创建的线程来处理它。 在第 6 次请求时,父 thread-local 属性值不会继承到子线程,因为我们没有创建新线程,而是重用池中已经创建的线程。
此代码片段将解释要点 -
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DeadlyComboTest {
public static void main(String[] args) throws InterruptedException {
int noOfThreads = 5;
//Execution 1
int threadPoolSize = noOfThreads;
//Execution 2 : uncomment below line and comment line above
//int threadPoolSize = noOfThreads/2;
//1. create thread pool
ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
//2. create Inheritable thread local
InheritableThreadLocal<Object> value = new InheritableThreadLocal<>();
//3. create new command and execute using thread pool created in step 1
for (int i = 1; i <= noOfThreads; i++) {
value.set(i);
executor.execute(() -> printThreadLocalValue(value));
}
executor.shutdown();
}
private static void printThreadLocalValue(ThreadLocal<Object> value) {
System.out.println(Thread.currentThread().getName() + " = " + value.get());
}
}
执行 1:noOfThreads = threadPoolSize = 5
OUTPUT: you may get the output in different sequence
pool-1-thread-1 = 1
pool-1-thread-2 = 2
pool-1-thread-3 = 3
pool-1-thread-4 = 4
pool-1-thread-5 = 5
一切看起来都很好。 根据每个请求创建新线程并正确创建子线程 继承父级的 thread-local 值。
执行 2:noOfThreads = 5 && threadPoolSize = noOfThreads/2 = 2 OUTPUT:
pool-1-thread-1 = 1
pool-1-thread-2 = 2
pool-1-thread-1 = 1
pool-1-thread-2 = 2
pool-1-thread-1 = 1
只有前两个请求 thread-local 继承工作正常, 由于线程池大小为 2,因此对于前两个请求新线程 得到创建和汇集。 第三个请求已经从池中创建的线程被重用 它仍然具有旧线程的继承值。
执行 2 是一个真实场景,池中的线程将被重用。
当我们使用线程池和 InheritableThreadLocal 组合时会出现什么问题?
这将导致意外的信息泄漏,因为池中的 worker/child 线程会将一个线程的 thread-local 属性值泄漏到另一个线程。
如果您的业务逻辑依赖于这些属性值,您将很难跟踪错误。
这就是为什么 spring 文档警告不要使用这种线程池和 InheritableThreadLocal 的组合。