36

ExecutorServiceとFuture(ここにサンプルコード)を使用して、タイムアウトのある別のスレッドでプロセスを実行しています(スレッドの「生成」はAOPアスペクトで行われます)。

現在、メインスレッドはResteasyリクエストです。Resteasyは、1つ以上のThreadLocal変数を使用して、Restメソッド呼び出しのある時点で取得する必要のあるコンテキスト情報を格納します。問題は、Resteasyスレッドが新しいスレッドで実行されているため、ThreadLocal変数が失われることです。

Resteasyが使用するThreadLocal変数を新しいスレッドに「伝播」するための最良の方法は何でしょうか。Resteasyはコンテキスト情報を追跡するために複数のThreadLocal変数を使用しているようです。私は、すべての情報を新しいスレッドに「盲目的に」転送したいと思います。

サブクラス化とbeforeExecuteThreadPoolExecutorメソッドを使用して現在のスレッドをプールに渡す方法を確認しましたが、ThreadLocal変数をプールに渡す方法が見つかりませんでした。

なにか提案を?

ありがとう

4

6 に答える 6

23

ThreadLocalスレッドに関連付けられたインスタンスのセットは、それぞれのプライベートメンバーに保持されますThread。これらを列挙する唯一のチャンスは、Thread;について熟考することです。このようにして、スレッドのフィールドのアクセス制限をオーバーライドできます。

のセットを取得できたら、のフックとフックを使用して、または呼び出しをインターセプトして必要なインスタンスの未設定を設定するタスクのラッパーを作成することによりThreadLocal、バックグラウンドスレッドにコピーできます。実際には、後者の手法の方がうまくいく可能性があります。これは、タスクがキューに入れられたときに値を格納するのに便利な場所を提供するためです。beforeExecute()afterExecute()ThreadPoolExecutorRunnablerun()ThreadLocalThreadLocal


更新:これは、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();
}
于 2011-08-31T16:33:25.083 に答える
5

@ericksonの回答に基づいて、私はこのコードを作成しました。これはinheritableThreadLocalsで機能しています。スレッドコンストラクタで使用されるのと同じメソッドを使用して、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;
   }
于 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ブロックを実行すると、その子スレッドが制御され、親のThreadLocalのものが子のThreadLocalに設定されます。

ここでの問題は、ThreadLocal全体がコピーされるわけではありません。LocaleContextのみがコピーされます。ThreadLocalは、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");
    }
}
于 2017-05-17T20:01:47.940 に答える
-4

ThreadLocalコードを見ると、次のことがわかります。

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

現在のスレッドは上書きできません。

可能な解決策:

  1. java 7のフォーク/結合メカニズムを見てください(しかし、それは悪い方法だと思います)

  2. JVMのクラスを上書きするための承認されたメカニズムを確認してください。ThreadLocal

  3. RESTEasyを書き直してみてください(IDEのリファクタリングツールを使用して、すべてのThreadLocalの使用法を置き換えることができます。簡単に見えます)

于 2011-08-31T16:24:38.800 に答える