将ThreadLocal传播到从ExecutorService获取的新线程

Luc*_*sio 29 java multithreading thread-local resteasy threadpool

我使用ExecutorService和Future(这里是示例代码)在一个带有超时的单独线程中运行一个进程(线程"生成"发生在AOP方面).

现在,主线程是Resteasy请求.Resteasy使用一个或多个ThreadLocal变量来存储我需要在Rest方法调用中的某个时刻检索的一些上下文信息.问题是,由于Resteasy线程在新线程中运行,因此ThreadLocal变量将丢失.

将Resteasy使用的任何ThreadLocal变量"传播"到新线程的最佳方法是什么?似乎Resteasy使用多个ThreadLocal变量来跟踪上下文信息,我想"盲目地"将所有信息传递给新线程.

我查看了子类化ThreadPoolExecutor并使用beforeExecute方法将当前线程传递给池,但我找不到将ThreadLocal变量传递给池的方法.

有什么建议吗?

谢谢

eri*_*son 20

ThreadLocal与线程关联的实例集保存在每个实例的私有成员中Thread.你唯一一个列举这些的机会就是对...进行反思Thread.这样,您可以覆盖线程字段的访问限制.

一旦你可以得到这一组ThreadLocal,你就可以使用beforeExecute()afterExecute()钩子在后台线程中复制ThreadPoolExecutor,或者通过Runnable为你的任务创建一个包装器来拦截run()调用以设置一个未设置的必要ThreadLocal实例.实际上,后一种技术可能会更好,因为它可以为您提供一个方便的位置来存储ThreadLocal任务排队时的值.


更新:这是第二种方法的更具体的说明.与我原来的描述相反,存储在包装器中的所有内容都是调用线程,在执行任务时会对其进行查询.

static Runnable wrap(Runnable task)
{
  Thread caller = Thread.currentThread();
  return () -> {
    Iterable<ThreadLocal<?>> vars = copy(caller);
    try {
      task.run();
    }
    finally {
      for (ThreadLocal<?> var : vars)
        var.remove();
    }
  };
}

/**
 * For each {@code ThreadLocal} in the specified thread, copy the thread's 
 * value to the current thread.  
 * 
 * @param caller the calling thread
 * @return all of the {@code ThreadLocal} instances that are set on current thread
 */
private static Collection<ThreadLocal<?>> copy(Thread caller)
{
  /* Use a nasty bunch of reflection to do this. */
  throw new UnsupportedOperationException();
}
Run Code Online (Sandbox Code Playgroud)


Mr *_*edi 5

基于@erickson 的回答,我写了这段代码。它适用于inheritableThreadLocals。它使用与线程构造函数中使用的方法相同的方法构建可继承线程本地列表。当然,我使用反射来做到这一点。我也覆盖了执行程序类。

public class MyThreadPoolExecutor extends ThreadPoolExecutor
{
   @Override
   public void execute(Runnable command)
   {
      super.execute(new Wrapped(command, Thread.currentThread()));
   }
}
Run Code Online (Sandbox Code Playgroud)

包装:

   private class Wrapped implements Runnable
   {
      private final Runnable task;

      private final Thread caller;

      public Wrapped(Runnable task, Thread caller)
      {
         this.task = task;
         this.caller = caller;
      }

      public void run()
      {
         Iterable<ThreadLocal<?>> vars = null;
         try
         {
            vars = copy(caller);
         }
         catch (Exception e)
         {
            throw new RuntimeException("error when coping Threads", e);
         }
         try {
            task.run();
         }
         finally {
            for (ThreadLocal<?> var : vars)
               var.remove();
         }
      }
   }
Run Code Online (Sandbox Code Playgroud)

复制方法:

public static Iterable<ThreadLocal<?>> copy(Thread caller) throws Exception
   {
      List<ThreadLocal<?>> threadLocals = new ArrayList<>();
      Field field = Thread.class.getDeclaredField("inheritableThreadLocals");
      field.setAccessible(true);
      Object map = field.get(caller);
      Field table = Class.forName("java.lang.ThreadLocal$ThreadLocalMap").getDeclaredField("table");
      table.setAccessible(true);

      Method method = ThreadLocal.class
              .getDeclaredMethod("createInheritedMap", Class.forName("java.lang.ThreadLocal$ThreadLocalMap"));
      method.setAccessible(true);
      Object o = method.invoke(null, map);

      Field field2 = Thread.class.getDeclaredField("inheritableThreadLocals");
      field2.setAccessible(true);
      field2.set(Thread.currentThread(), o);

      Object tbl = table.get(o);
      int length = Array.getLength(tbl);
      for (int i = 0; i < length; i++)
      {
         Object entry = Array.get(tbl, i);
         Object value = null;
         if (entry != null)
         {
            Method referentField = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry").getMethod(
                    "get");
            referentField.setAccessible(true);
            value = referentField.invoke(entry);
            threadLocals.add((ThreadLocal<?>) value);
         }
      }
      return threadLocals;
   }
Run Code Online (Sandbox Code Playgroud)