ExecutorService线程不继承InheritableThreadLocal值

nfs*_*ake 5 java concurrency scala thread-local

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)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

产量

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
Run Code Online (Sandbox Code Playgroud)

我期待输出的最后一行中的"NewInitialValue",因为333 Thread产生了Thread 444,而tl是一个可继承的本地线程.

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

Sot*_*lis 10

InheritableThreadLocal当您无法控制线程的创建时,您不应该依赖它.javadoc说:

[...]创建子线程时,子进程接收父对象具有值的所有可继承线程局部变量的初始值.

在您的示例中,线程由ExecutorService返回的创建Executors.newFixedThreadPool(2)

这是一个执行器,它将使用最多两个线程来执行您的任务.来自javadoc

创建一个线程池,该线程池重用在共享的无界队列中运行的固定数量的线程.在任何时候,最多nThreads线程都将是活动处理任务.如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,直到线程可用.

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

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

怎么解决

如果不知道自己想做什么,这很难回答.但是,如果你想有效地使用InheritableThreadLocal它,那一切都归结为知道和控制线程的创建,因此也就是继承链.

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

类似地,您可以使用另一种机制来传播该值:一个AtomicReference或不可变值的lambda捕获.