简体   繁体   中英

InheritableThreadLocal and thread pools

I have a problem which I don't really think has a solution but I'll try here anyway. My application uses a thread pool and some of the threads in this pool have an inheritable thread local variable. I've extended the ThreadPoolExecutor class to essentially clear out the thread local variable (in the afterExecute call back method) when a thread is done executing.

I understand that when you have an InheritableThreadLocal variable, the childValue() method is called when the thread is initialized to get the ThreadLocal variable's value from the parent thread. However, in my case the next time the thread is used (after being used once), the value of the InheritableThreadLocal variable is null (because it was previously cleared out in afterExecute). Is there a way to access the parent thread's thread local variable in beforeExecute so that I can essentially simulate what the childValue method in InheritableThreadLocal does at the time of thread creation.

It sounds like this is a poor use-case for the "inheritable" flavour of thread-locals.

My advice would be to just use a regular TheadLocal and do the initialization explicitly; eg passing the initial value from the parent thread to the child thread as a parameter or something.

(I was going suggest that you force initialization of the thread-local the child thread by having it fetch the value as soon as it starts. But that risks a race-condition; eg if the parent thread is returned to the pool before the child thread starts executing.)


I guess what I am asking is if there is a way to access the value of the parent thread's thread local variable from a child thread.

There isn't a way to do this.

And, judging from your other comments, I doubt that you mean "parent" and "child" in the normal sense ... where the parent thread creates the child thread.

But here's an idea. Instead of trying to share a variable between threads, share a fixed value (eg a request ID), and use that as a key for a shared Map . Use the Map entries as the shared variables.

The constructor of a runnable runs in the thread of the caller, whereas the run method runs in the child thread. You can use this fact to transfer information from the parent to the child thread. See:

public class ThreadlocalApplication {
static public ThreadLocal<String> tenantId = new ThreadLocal<>();

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executor = Executors.newCachedThreadPool();
    System.out.println(Thread.currentThread().getName());
    ThreadlocalApplication.tenantId.set("4711");
    executor.submit(new AsyncTask()).get();
    executor.shutdown();
}

static class AsyncTask implements Runnable {
    private String _tenantId;

    public AsyncTask() {
        System.out.println(Thread.currentThread().getName());
        _tenantId = ThreadlocalApplication.tenantId.get();
    }

    @Override
    public void run() {
        ThreadlocalApplication.tenantId.set(_tenantId);
        System.out.println(Thread.currentThread().getName());
        System.out.println(ThreadlocalApplication.tenantId.get());
    }
}
}

And this is the result

main
main
pool-1-thread-1
4711

I had a similar problem and ended up creating a ForkListeningExecutorService which wraps an ExecutorService (see here ). The wrapper sends events to a listener whenever a task is submitted to the executor or when it ends. You can then use the listener to pass whatever you want across the threads. This is how the Listener looks like:

  /**
   * Listener that will receive the events around task submission.
   *
   */
  public interface ExecutorServiceListener {

    /**
     * Will be called <b>before</b> any task is submitted to the service. This method will therefore run on the original "parent" thread.
     */
    default void beforeTaskSubmission() {
      // to be overridden by implementations
    }

    /**
     * Will be called <b>after</b> a task is submitted to the service and <b>before</b> the actual execution starts. This method will therefore run on the new "child" thread.
     */
    default void afterTaskSubmission() {
      // to be overridden by implementations
    }

    /**
     * Will be called <b>before</b> a submitted task ends no matter if an exception was thrown or not. This method will therefore run on the new "child" thread just before it's
     * released.
     */
    default void beforeTaskEnds() {
      // to be overridden by implementations
    }
  }

So, instead of extending ThreadPoolExecutor , you can simply wrap it (or whatever other ExecutorService implementation) and pass the state within the listener like this:

public class ForkListeningExecutorServiceExample {

  private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

  private static void printThreadMessage(final String message) {
    System.out.println(message
        + ", current thread: " + Thread.currentThread().getName()
        + ", value from threadLocal: " + threadLocal.get());
  }

  public static void main(final String[] args) throws Exception {

    threadLocal.set("MY_STATE");

    final ExecutorService executorService = new ForkListeningExecutorService(
        Executors.newCachedThreadPool(),
        new ExecutorServiceListener() {

          private String valueToShare;

          @Override
          public void beforeTaskSubmission() {
            valueToShare = threadLocal.get();
            printThreadMessage("The task is about to be submitted");
          }

          @Override
          public void afterTaskSubmission() {
            threadLocal.set(valueToShare);
            printThreadMessage("The task has been submitted and will start now");
          }

          @Override
          public void beforeTaskEnds() {
            threadLocal.set(null);
            printThreadMessage("The task has finished and thread will be released now");
          }
        });

    executorService.submit(() -> {
      printThreadMessage("The task is running now");
    }).get();

    printThreadMessage("We are back on the main thread");
  }
}

The output looks then like this:

The task is about to be submitted, current thread: main, value from threadLocal: MY_STATE
The task has been submitted and will start now, current thread: pool-1-thread-1, value from threadLocal: MY_STATE
The task is running now, current thread: pool-1-thread-1, value from threadLocal: MY_STATE
The task has finished and thread will be released now, current thread: pool-1-thread-1, value from threadLocal: null
We are back on the main thread, current thread: main, value from threadLocal: MY_STATE

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM