InheritableThreadLocal value not inherited by ExecutorService threads

8.7k views Asked by At
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)
    }
  }
}

Output

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

I was expecting "NewInitialValue" in the last line of output since 333 Thread spawned of Thread 444 and tl is a Inheritable thread local.

What is causing this issue and how can it be resolved ?

3

There are 3 answers

0
Sotirios Delimanolis On BEST ANSWER

You shouldn't rely on InheritableThreadLocal when you don't have control over the creation of threads. The javadoc states:

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

In your example, threads are being created by the ExecutorService returned by Executors.newFixedThreadPool(2)

That's an executor that will use up to two threads to execute your tasks. From the 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.

This is an implementation detail, but those threads are created lazily, as needed. When you submit the first task, 111, the call to submit will create and start a new thread. This new thread will inherit the value InitialValue. Similarly, when this thread submits the second task, 222, its call to submit will force the creation of the second thread which will also inherit the InitialValue.

Then you submit the third task, 333, overwrite the InheritableThreadLocal's value and print it. When you submit the fourth task 444, the ExecutorService uses existing threads to execute it. That thread already has a value, inherited earlier.

how can it be resolved

That's hard to answer without knowing what you want to do. But, if you want to effectively use InheritableThreadLocal, it all comes down to knowing and controlling the creation of threads, and therefore the inheritance chain.

You could create and use an ExecutorService that creates and uses a new thread for each submitted task, for example.

Similarly, you could use another mechanism to propagate that value: an AtomicReference or lambda capture of an immutable value.

0
The_Tourist On

If you look at the thread names in the output, you can see that there are two threads(as per your ExecutionContext configuration) pool-1-thread-1 and pool-1-thread-2.

444 thread is reusing the thread pool-1-thread-2 which was previously used by 222 with tl already assigned.

333 thread is reusing pool-1-thread-1 which was previously used by 111 with tl already assigned, but it's overwriting the inherited InitialValue to NewInitialValue.

You might see a different output if you increase the no. of threads. This is what I get with

three threads:

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"

four threads:

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"
0
vkg On

I am using Spring Integration and using an executor to process split messages. Faced the same issue during this.

@Soritos is right

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

What workaround I did was

  1. Added the ThreadLocal variable in MessageHeaders.
  2. In the message Splitter, created a new InhertiableThreadLocal and assign value from the MessageHeaders

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