简体   繁体   English

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

[英]Propagating ThreadLocal to a new Thread fetched from a ExecutorService

I'm running a process in a separate thread with a timeout, using an ExecutorService and a Future (example code here ) (the thread "spawning" takes place in a AOP Aspect). 我使用ExecutorService和Future( 这里是示例代码)在一个带有超时的单独线程中运行一个进程(线程“生成”发生在AOP方面)。

Now, the main thread is a Resteasy request. 现在,主线程是Resteasy请求。 Resteasy uses one ore more ThreadLocal variables to store some context information that I need to retrieve at some point in my Rest method call. Resteasy使用一个或多个ThreadLocal变量来存储我需要在Rest方法调用中的某个时刻检索的一些上下文信息。 Problem is, since the Resteasy thread is running in a new thread, the ThreadLocal variables are lost. 问题是,由于Resteasy线程在新线程中运行,因此ThreadLocal变量将丢失。

What would be the best way to "propagate" whatever ThreadLocal variable is used by Resteasy to the new thread? 将Resteasy使用的任何ThreadLocal变量“传播”到新线程的最佳方法是什么? It seems that Resteasy uses more than one ThreadLocal variable to keep track of context information and I would like to "blindly" transfer all the information to the new thread. 似乎Resteasy使用多个ThreadLocal变量来跟踪上下文信息,我想“盲目地”将所有信息传递给新线程。

I have looked at subclassing ThreadPoolExecutor and using the beforeExecute method to pass the current thread to the pool, but I couldn't find a way to pass the ThreadLocal variables to the pool. 我已经查看了子类化ThreadPoolExecutor并使用beforeExecute方法将当前线程传递给池,但我找不到将ThreadLocal变量传递给池的方法。

Any suggestion? 有什么建议吗?

Thanks 谢谢

The set of ThreadLocal instances associated with a thread are held in private members of each Thread . 与线程关联的ThreadLocal实例集保存在每个Thread私有成员中。 Your only chance to enumerate these is to do some reflection on the Thread ; 你唯一枚举这些的机会就是对Thread做一些反思; this way, you can override the access restrictions on the thread's fields. 这样,您可以覆盖线程字段的访问限制。

Once you can get the set of ThreadLocal , you could copy in the background threads using the beforeExecute() and afterExecute() hooks of ThreadPoolExecutor , or by creating a Runnable wrapper for your tasks that intercepts the run() call to set an unset the necessary ThreadLocal instances. 一旦你可以获得ThreadLocal的集合,你可以使用ThreadPoolExecutorbeforeExecute()afterExecute()钩子在后台线程中复制,或者为你的任务创建一个Runnable包装器来拦截run()调用以设置unset必要的ThreadLocal实例。 Actually, the latter technique might work better, since it would give you a convenient place to store the ThreadLocal values at the time the task is queued. 实际上,后一种技术可能效果更好,因为它可以为您提供在任务排队时存储ThreadLocal值的便利位置。


Update: Here's a more concrete illustration of the second approach. 更新:这是第二种方法的更具体的说明。 Contrary to my original description, all that is stored in the wrapper is the calling thread, which is interrogated when the task is executed. 与我原来的描述相反,存储在包装器中的所有内容都是调用线程,在执行任务时会对其进行查询。

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();
}

Based on @erickson answer I wrote this code. 根据@erickson的回答我写了这段代码。 It is working for inheritableThreadLocals. 它适用于inheritableThreadLocals。 It builds list of inheritableThreadLocals using same method as is used in Thread contructor. 它使用与Thread构造函数中使用的方法相同的方法构建inheritableThreadLocals的列表。 Of course I use reflection to do this. 当然我用反射来做到这一点。 Also I override the executor class. 我也覆盖了执行者类。

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

Wrapper: 包装:

   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();
         }
      }
   }

copy method: 复制方法:

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;
   }

正如我理解你的问题,你可以看一下InheritableThreadLocal ,它意味着将ThreadLocal变量从Parent Thread上下文传递给Child Thread Context。

Here is an example to pass the current LocaleContext in parent thread to the child thread spanned by CompletableFuture[By default it used ForkJoinPool]. 下面是一个示例,将父线程中的当前LocaleContext传递给CompletableFuture跨越的子线程[默认使用ForkJoinPool]。

Just define all the things you wanted to do in a child thread inside a Runnable block. 只需在Runnable块中的子线程中定义您想要执行的所有操作。 So when the CompletableFuture execute the Runnable block, its the child thread who is in control and voila you have the parent's ThreadLocal stuff set in Child's ThreadLocal. 因此,当CompletableFuture执行Runnable块时,它的子线程处于控制状态,并且你在Child的ThreadLocal中设置了父亲的ThreadLocal东西。

The problem here is not the entire ThreadLocal is copied over. 这里的问题不是整个ThreadLocal被复制过来。 Only the LocaleContext is copied. 仅复制LocaleContext。 Since the ThreadLocal is of private access to only the Thread it belongs too using Reflection and trying to get and set in Child is all too much of wacky stuff which might lead to memory leaks or performance hit. 由于ThreadLocal只能私有访问它所属的Thread,因此使用Reflection并尝试在Child中获取和设置是太多古怪的东西,可能导致内存泄漏或性能损失。

So if you know the parameters you are interested from the ThreadLocal, then this solution works way cleaner. 因此,如果您从ThreadLocal中了解了您感兴趣的参数,那么此解决方案的工作方式更加清晰。

 public void parentClassMethod(Request request) {
        LocaleContext currentLocale = LocaleContextHolder.getLocaleContext();
        executeInChildThread(() -> {
                LocaleContextHolder.setLocaleContext(currentLocale);
                //Do whatever else you wanna do
            }));

        //Continue stuff you want to do with parent thread
}


private void executeInChildThread(Runnable runnable) {
    try {
        CompletableFuture.runAsync(runnable)
            .get();
    } catch (Exception e) {
        LOGGER.error("something is wrong");
    }
}

I don't like Reflection approach. 我不喜欢反思方法。 Alternative solution would be to implement executor wrapper and pass object directly as a ThreadLocal context to all child threads propagating a parent context. 替代解决方案是实现执行程序包装器并将对象作为ThreadLocal上下文直接传递给传播父上下文的所有子线程。

public class PropagatedObject {

    private ThreadLocal<ConcurrentHashMap<AbsorbedObjectType, Object>> data = new ThreadLocal<>();

   //put, set, merge methods, etc

}

==> ==>

public class ObjectAwareExecutor extends AbstractExecutorService {

    private final ExecutorService delegate;
    private final PropagatedObject objectAbsorber;

    public ObjectAwareExecutor(ExecutorService delegate, PropagatedObject objectAbsorber){
        this.delegate = delegate;
        this.objectAbsorber = objectAbsorber;
    }
    @Override
    public void execute(final Runnable command) {

        final ConcurrentHashMap<String, Object> parentContext = objectAbsorber.get();
        delegate.execute(() -> {
            try{
                objectAbsorber.set(parentContext);
                command.run();
            }finally {
                parentContext.putAll(objectAbsorber.get());
                objectAbsorber.clean();
            }
        });
        objectAbsorber.merge(parentContext);
    }

If you look at ThreadLocal code you can see: 如果您查看ThreadLocal代码,您可以看到:

    public T get() {
        Thread t = Thread.currentThread();
        ...
    }

current thread cannot be overwritten. 当前线程不能被覆盖。

Possible solutions: 可能的解决方案:

  1. Look at java 7 fork/join mechanism (but i think it's a bad way) 看看java 7 fork / join机制(但我认为这是一个糟糕的方式)

  2. Look at endorsed mechanism to overwrite ThreadLocal class in your JVM. 查看已批准的机制来覆盖JVM中的ThreadLocal类。

  3. Try to rewrite RESTEasy (you can use Refactor tools in your IDE to replace all ThreadLocal usage, it's look like easy) 尝试重写RESTEasy(您可以在IDE中使用Refactor工具替换所有ThreadLocal用法,看起来很简单)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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