36

我正在使用 ExecutorService 和 Future(此处的示例代码)在一个带有超时的单独线程中运行一个进程(线程“生成”发生在 AOP 方面)。

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

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

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

有什么建议吗?

谢谢

4

6 回答 6

23

与线程关联的一组ThreadLocal实例保存在每个Thread. 您列举这些的唯一机会是对Thread; 这样,您可以覆盖线程字段的访问限制。

一旦您获得了 的集合,您就可以使用和的钩子ThreadLocal在后台线程中复制,或者通过为您的任务创建一个包装器来拦截调用以设置未设置的必要实例。实际上,后一种技术可能效果更好,因为它可以为您在任务排队时存储值提供一个方便的位置。beforeExecute()afterExecute()ThreadPoolExecutorRunnablerun()ThreadLocalThreadLocal


更新:这是第二种方法的更具体的说明。与我最初的描述相反,包装器中存储的所有内容都是调用线程,在执行任务时会询问该线程。

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();
}
于 2011-08-31T16:33:25.083 回答
5

根据@erickson 的回答,我编写了这段代码。它适用于可继承的ThreadLocals。它使用与 Thread 构造函数中使用的相同方法构建可继承的ThreadLocals 列表。当然,我使用反射来做到这一点。我也覆盖了执行者类。

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

包装:

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

复制方法:

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;
   }
于 2017-02-14T10:03:26.467 回答
1

据我了解您的问题,您可以查看InheritableThreadLocal,它旨在将ThreadLocal变量从父线程上下文传递到子线程上下文

于 2011-08-31T16:22:32.220 回答
1

我不喜欢反射方法。另一种解决方案是实现执行程序包装器并将对象作为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);
    }
于 2017-11-01T18:06:14.140 回答
0

这是一个将父线程中的当前 LocaleContext 传递给 CompletableFuture [默认情况下它使用 ForkJoinPool] 跨越的子线程的示例。

只需在 Runnable 块内的子线程中定义您想要做的所有事情。因此,当 CompletableFuture 执行 Runnable 块时,它是受控制的子线程,瞧,您在 Child 的 ThreadLocal 中设置了父线程的 ThreadLocal 内容。

这里的问题不是整个 ThreadLocal 都被复制过来了。仅复制 LocaleContext。由于 ThreadLocal 只能对它所属的 Thread 进行私有访问,因此使用反射并尝试在 Child 中获取和设置太多古怪的东西,这可能会导致内存泄漏或性能下降。

因此,如果您从 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");
    }
}
于 2017-05-17T20:01:47.940 回答
-4

如果您查看 ThreadLocal 代码,您可以看到:

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

当前线程不能被覆盖。

可能的解决方案:

  1. 看看 java 7 fork/join 机制(但我认为这是一个坏方法)

  2. 查看在您的 JVM中覆盖类的认可机制。ThreadLocal

  3. 尝试重写 RESTEasy(你可以在你的 IDE 中使用 Refactor 工具来替换所有 ThreadLocal 的使用,看起来很简单)

于 2011-08-31T16:24:38.800 回答