InheritableThreadLocal 值未被 ExecutorService 线程继承

InheritableThreadLocal value not inherited by ExecutorService threads

import java.util.concurrent.Executors
import scala.concurrent.{ExecutionContext, Future}

object TestInheritableThreadLocal {

  def main(args: Array[String]): Unit = {

    implicit val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(2))

    val tl: InheritableThreadLocal[String] = new InheritableThreadLocal[String]()
    tl.set("InitialValue")

    Future {
      println("111 " + Thread.currentThread() + tl.get())
      Future {
        println("222 " + Thread.currentThread() + tl.get())
      }
    }
    Thread.sleep(3000)

    Future {
      tl.set("NewInitialValue")
      println("333 " + Thread.currentThread() + tl.get())
      Future {
        println("444 " + Thread.currentThread() + tl.get())
      }
      Thread.sleep(3000)
    }
  }
}

输出

111 Thread[pool-1-thread-1,5,main]InitialValue
222 Thread[pool-1-thread-2,5,main]InitialValue
333 Thread[pool-1-thread-1,5,main]NewInitialValue
444 Thread[pool-1-thread-2,5,main]InitialValue

我期待在输出的最后一行出现 "NewInitialValue",因为 333 线程从线程 444 派生,并且 tl 是一个可继承的本地线程。

是什么导致了这个问题,如何解决?

当您无法控制线程的创建时,您不应该依赖 InheritableThreadLocal。 javadoc 指出:

[...] when a child thread is created, the child receives initial values for all inheritable thread-local variables for which the parent has values.

在您的示例中,线程是由 Executors.newFixedThreadPool(2)

返回的 ExecutorService 创建的

这是一个执行器,最多可以使用两个线程来执行您的任务。来自 javadoc

Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue. At any point, at most nThreads threads will be active processing tasks. If additional tasks are submitted when all threads are active, they will wait in the queue until a thread is available.

这是一个实现细节,但这些线程是根据需要延迟创建的。当您提交第一个任务 111 时,对 submit 的调用将创建并启动一个新线程。这个新线程将继承值 InitialValue。同样,当该线程提交第二个任务 222 时,它对 submit 的调用将强制创建第二个线程,该线程也将继承 InitialValue.

然后你提交第三个任务,333,覆盖InheritableThreadLocal的值并打印它。当您提交第四个任务 444 时,ExecutorService 使用现有线程来执行它。该线程已经有一个值,继承较早。

how can it be resolved

如果不知道你想做什么,这很难回答。但是,如果您想有效地使用 InheritableThreadLocal,一切都归结为了解和控制线程的创建,以及继承链。

例如,您可以创建并使用一个 ExecutorService,它为每个提交的任务创建并使用一个新线程。

同样,您可以使用另一种机制来传播该值:不可变值的 AtomicReference 或 lambda 捕获。

如果查看输出中的线程名称,您可以看到有两个线程(根据您的 ExecutionContext 配置)pool-1-thread-1pool-1-thread-2

444 线程正在重用线程 pool-1-thread-2,该线程先前由 222 使用并且 tl 已分配。

333 线程正在重用 111 之前使用的 pool-1-thread-1,并且已经分配了 tl,但它正在将继承的 InitialValue 覆盖为 NewInitialValue.

如果增加编号,您可能会看到不同的输出。线程。这就是我用

得到的

三个线程:

111 Thread[pool-1-thread-1,5,main]InitialValue
222 Thread[pool-1-thread-2,5,main]InitialValue
333 Thread[pool-1-thread-3,5,main]NewInitialValue
444 Thread[pool-1-thread-2,5,main]InitialValue // Reusing "pool-1-thread-2"

四个线程:

111 Thread[pool-1-thread-1,5,main]InitialValue
222 Thread[pool-1-thread-2,5,main]InitialValue
333 Thread[pool-1-thread-3,5,main]NewInitialValue
444 Thread[pool-1-thread-4,5,main]NewInitialValue // Fresh thread "pool-1-thread-4"

我正在使用 Spring 集成并使用执行器来处理拆分消息。 在此期间遇到了同样的问题。

@Soritos 是对的

That's hard to answer without knowing what you want to do

我所做的解决方法是

  1. 在 MessageHeaders 中添加了 ThreadLocal 变量。
  2. 在message Splitter中,创建了一个新的InhertiableThreadLocal并从MessageHeaders中赋值

     if (null != message.getHeaders().get("frameworkCorrelationID")) {
        private static final InheritableThreadLocal<String> id = new InheritableThreadLocal(); 
        id.set((String)message.getHeaders().get("frameworkCorrelationID"));    }