ExecutorService线程不继承InheritableThreadLocal值

时间:2016-12-23 00:10:17

标签: java scala concurrency 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)
    }
  }
}

输出

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 Thread生成了Thread 444,而tl是一个可继承的本地线程。

导致此问题的原因是什么以及如何解决?

3 个答案:

答案 0 :(得分:10)

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

  

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

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

返回的ExecutorService创建

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

  

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

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

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

  

如何解决

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

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

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

答案 1 :(得分:0)

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

444主题正在重复使用pool-1-thread-2之前已使用222已经分配的主题tl

333主题重新使用pool-1-thread-1 111tl之前已使用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" }。

如果增加no,您可能会看到不同的输出。线程。这是我得到的

三个主题:

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"

四个主题:

select q.project from Status q
where q.name = 'new'
  and q.id in (select max(sq.id) from Status sq group by sq.project.id)

答案 2 :(得分:0)

我正在使用Spring Integration并使用执行程序来处理拆分消息。 在这期间面临同样的问题。

@Soritos是对的

  

在不知道自己想做什么的情况下很难回答

我做了哪些解决方法

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

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