InheritableThreadLocal 扩展了 ThreadLocal 并在我们需要在创建时将父线程本地属性值自动传递给子线程时使用。
当您每次都创建新的子线程而不重用已创建的线程时,这种继承工作得很好。
让我们考虑一个场景 - 我们有大小为 5 的线程池。当我们将前 5 个任务发送到线程池以处理每个任务时,会创建新线程,因此线程本地属性的继承工作得非常好。问题从第 6 个请求开始。当您发送第 6 个任务时,不会创建新线程,但已从线程池中创建的线程会被重用于处理它。在第 6 次请求时,父线程本地属性值不会被继承到子线程,因为我们不是创建新线程而是重用池中已经创建的线程。
此代码片段将解释这一点 -
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
一切看起来都很好。在每个请求中,都会创建新线程,并且子线程正确地继承父线程的本地线程值。
执行 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
仅对于前两个请求,线程本地继承正常工作,因为线程池大小为 2,因此对于前两个请求,新线程被创建和池化。第三个请求已经从池中创建的线程被重用,它们仍然具有旧线程的继承值。
执行 2 是一个真实世界的场景,池中的线程将被重用。
当我们使用线程池和 InheritableThreadLocal 组合时会出现什么问题?
这将导致意外的信息泄漏,因为池中的工作/子线程会将一个线程的线程本地属性值泄漏到另一个线程。
如果您的业务逻辑依赖于这些属性值,您将很难跟踪错误。
这就是为什么 spring 文档警告不要使用线程池和 InheritableThreadLocal 的组合。