我正在使用ExecutorService和Future(示例代码here)在具有超时的单独线程中运行进程(线程“生成”发生在AOP方面)。
现在,主要线程是Resteasy请求。 Resteasy使用一个或多个ThreadLocal变量来存储我需要在Rest方法调用中的某个时刻检索的一些上下文信息。问题是,由于Resteasy线程在新线程中运行,ThreadLocal变量将丢失。
将Resteasy使用的任何ThreadLocal变量“传播”到新线程的最佳方法是什么?似乎Resteasy使用多个ThreadLocal变量来跟踪上下文信息,我想“盲目地”将所有信息传输到新线程。
我已经查看了子类ThreadPoolExecutor
并使用beforeExecute方法将当前线程传递给池,但我找不到将ThreadLocal变量传递给池的方法。
有什么建议吗?
由于
答案 0 :(得分:20)
与线程关联的ThreadLocal
个实例集合保存在每个Thread
的私有成员中。您枚举这些内容的唯一机会是对Thread
进行一些反思;这样,您可以覆盖线程字段的访问限制。
获得ThreadLocal
的设置后,您可以使用beforeExecute()
的{{1}}和afterExecute()
挂钩复制后台主题,或者创建ThreadPoolExecutor
1}}用于截取Runnable
调用的任务的包装器,用于设置未设置的必要run()
实例。实际上,后一种技术可能会更好,因为它可以为您提供一个方便的位置来存储任务排队时的ThreadLocal
值。
更新:以下是第二种方法的更具体的说明。与我原来的描述相反,存储在包装器中的所有内容都是调用线程,在执行任务时会被询问。
ThreadLocal
答案 1 :(得分:3)
根据我的理解你的问题,你可以查看InheritableThreadLocal,这是为了将ThreadLocal
变量从父线程上下文传递给子线程上下文
答案 2 :(得分:3)
根据@erickson的回答我写了这段代码。它适用于inheritableThreadLocals。它使用与Thread构造函数中使用的方法相同的方法构建inheritableThreadLocals的列表。当然我用反射来做到这一点。我也覆盖了执行者类。
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;
}
答案 3 :(得分:0)
下面是一个示例,将父线程中的当前LocaleContext传递给CompletableFuture跨越的子线程[默认使用ForkJoinPool]。
只需在Runnable块中的子线程中定义您想要执行的所有操作。因此,当CompletableFuture执行Runnable块时,它的子线程处于控制状态,并且你在Child的ThreadLocal中设置了父亲的ThreadLocal东西。
这里的问题不是整个ThreadLocal被复制过来。仅复制LocaleContext。由于ThreadLocal只能私有访问它所属的Thread,因此使用Reflection并尝试在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");
}
}
答案 4 :(得分:0)
我不喜欢反思方法。替代解决方案是实现执行程序包装器并将对象直接作为ThreadLocal
上下文传递给传播父上下文的所有子线程。
public class PropagatedObject {
private ThreadLocal<ConcurrentHashMap<AbsorbedObjectType, Object>> data = new ThreadLocal<>();
//put, set, merge methods, etc
}
==&GT;
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);
}
答案 5 :(得分:-3)
如果查看ThreadLocal代码,您可以看到:
public T get() {
Thread t = Thread.currentThread();
...
}
当前线程无法覆盖。
可能的解决方案:
看看java 7 fork / join机制(但我认为这是一个糟糕的方式)
查看endorsed机制以覆盖JVM中的ThreadLocal
类。
尝试重写RESTEasy(您可以在IDE中使用Refactor工具替换所有ThreadLocal用法,看起来很简单)