ExecutorServiceとFutureを使用して、タイムアウトのある別のスレッドでプロセスを実行しています(サンプルコード here )(スレッドの "発生"はAOPアスペクトで行われます)。
現在、メインスレッドは Resteasy リクエストです。 Resteasyは1つ以上のThreadLocal変数を使用して、Restメソッド呼び出しのある時点で取得する必要があるコンテキスト情報を格納します。問題は、Resteasyスレッドが新しいスレッドで実行されているため、ThreadLocal変数が失われることです。
Resteasyが使用するThreadLocal変数を新しいスレッドに「伝播」する最良の方法は何でしょうか。 Resteasyは複数のThreadLocal変数を使用してコンテキスト情報を追跡しているようです。すべての情報を新しいスレッドに「盲目的に」転送したいと思います。
ThreadPoolExecutor
のサブクラス化と beforeExecute メソッドを使用して現在のスレッドをプールに渡す方法を確認しましたが、ThreadLocal変数をプールに渡す方法が見つかりませんでした。
なにか提案を?
ありがとう
スレッドに関連付けられたThreadLocal
インスタンスのセットは、各Thread
のプライベートメンバーに保持されます。これらを列挙する唯一のチャンスは、Thread
について考察することです。このようにして、スレッドのフィールドに対するアクセス制限を上書きできます。
ThreadLocal
のセットを取得したら、ThreadPoolExecutor
のbeforeExecute()
およびafterExecute()
フックを使用するか、またはタスクのRunnable
ラッパー。run()
呼び出しをインターセプトして、必要なThreadLocal
インスタンスの設定を解除します。実際、後者の手法は、タスクがキューに入れられたときにThreadLocal
値を格納するための便利な場所を提供するため、より効果的に機能する可能性があります。
pdate: 2番目のアプローチのより具体的な例を次に示します。元の説明とは逆に、ラッパーに格納されるのは呼び出しスレッドだけであり、タスクが実行されるときに問い合わせられます。
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のリストを作成します。もちろん、リフレクションを使用してこれを行います。また、executorクラスをオーバーライドします。
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;
}
あなたの問題を理解しているので、親スレッドコンテキストから子スレッドコンテキストにThreadLocal
変数を渡すことを意味する InheritableThreadLocal を見ることができます。
次に、親スレッドの現在のLocaleContextをCompletableFutureが張った子スレッドに渡す例を示します[デフォルトではForkJoinPoolを使用しました]。
Runnableブロック内の子スレッドで実行するすべてのことを定義するだけです。したがって、CompletableFutureがRunnableブロックを実行すると、その子スレッドが制御されて、子の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");
}
}
リフレクションのアプローチは好きではありません。別の解決策は、executorラッパーを実装し、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);
}