[英]Propagating ThreadLocal to a new Thread fetched from a ExecutorService
我使用ExecutorService和Future( 這里是示例代碼)在一個帶有超時的單獨線程中運行一個進程(線程“生成”發生在AOP方面)。
現在,主線程是Resteasy請求。 Resteasy使用一個或多個ThreadLocal變量來存儲我需要在Rest方法調用中的某個時刻檢索的一些上下文信息。 問題是,由於Resteasy線程在新線程中運行,因此ThreadLocal變量將丟失。
將Resteasy使用的任何ThreadLocal變量“傳播”到新線程的最佳方法是什么? 似乎Resteasy使用多個ThreadLocal變量來跟蹤上下文信息,我想“盲目地”將所有信息傳遞給新線程。
我已經查看了子類化ThreadPoolExecutor
並使用beforeExecute方法將當前線程傳遞給池,但我找不到將ThreadLocal變量傳遞給池的方法。
有什么建議嗎?
謝謝
與線程關聯的ThreadLocal
實例集保存在每個Thread
私有成員中。 你唯一枚舉這些的機會就是對Thread
做一些反思; 這樣,您可以覆蓋線程字段的訪問限制。
一旦你可以獲得ThreadLocal
的集合,你可以使用ThreadPoolExecutor
的beforeExecute()
和afterExecute()
鈎子在后台線程中復制,或者為你的任務創建一個Runnable
包裝器來攔截run()
調用以設置unset必要的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();
}
根據@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;
}
正如我理解你的問題,你可以看一下InheritableThreadLocal ,它意味着將ThreadLocal
變量從Parent Thread上下文傳遞給Child Thread Context。
下面是一個示例,將父線程中的當前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");
}
}
我不喜歡反思方法。 替代解決方案是實現執行程序包裝器並將對象作為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);
}
如果您查看ThreadLocal代碼,您可以看到:
public T get() {
Thread t = Thread.currentThread();
...
}
當前線程不能被覆蓋。
可能的解決方案:
看看java 7 fork / join機制(但我認為這是一個糟糕的方式)
查看已批准的機制來覆蓋JVM中的ThreadLocal
類。
嘗試重寫RESTEasy(您可以在IDE中使用Refactor工具替換所有ThreadLocal用法,看起來很簡單)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.