web-dev-qa-db-ja.com

ExecutorServiceでThreadLocalを使用するのは危険ですか?

以下のブログでThreadLocalsのコンセプトを調べていました。

https://www.baeldung.com/Java-threadlocal

「ExecutorServiceでThreadLocalを使用しないでください」とあります

ThreadLocalsの使用例を以下に示します。

public class ThreadLocalWithUserContext implements Runnable {

    private static ThreadLocal<Context> userContext 
      = new ThreadLocal<>();
    private Integer userId;
    private UserRepository userRepository = new UserRepository();

    @Override
    public void run() {
        String userName = userRepository.getUserNameForUserId(userId);
        userContext.set(new Context(userName));
        System.out.println("thread context for given userId: "
          + userId + " is: " + userContext.get());
    }

    // standard constructor
}

投稿の最後で、次のように述べています。

ExecutorServiceを使用してRunnableを送信する場合、ThreadLocalを使用すると非決定的な結果が得られます。特定のuserIdのすべてのRunnableアクションが、実行されるたびに同じスレッドによって処理されるという保証がないためです。 。

そのため、ThreadLocalは異なるuserId間で共有されます。そのため、ExecutorServiceと一緒にTheadLocalを使用しないでください。実行する実行可能なアクションを選択するスレッドを完全に制御できる場合にのみ使用してください。

この説明は私にとって警戒心を示しました。この点について具体的にオンラインで調査を試みましたが、あまり助けが得られませんでした。専門家が上記の説明について詳しく説明してもらえますか?それは作者の見解なのか、それとも本当の脅威なのか?

10
Hitesh

ThreadLocalは、その変数を設定した後にキャッシュしたいときに使用します。したがって、次にアクセスするときは、初期化せずにThreadLocalから直接取得できます。

threadLocal.set(obj)を使用して設定し、スレッド内でthreadLocal.get()を介してアクセスするため、直接スレッドセーフな保証が得られます。

しかし、明示的にthreadLocal.remove()によってcacheをクリアしないと、物事は醜くなるかもしれません。

  1. スレッドプールでは、キューに入れられたタスクはスレッドによって1つずつ処理され、ほとんどの場合タスクは独立しているはずですが、スレッドスコープのキャッシュthreadLocalは、クリアするのを忘れた場合、次のタスクを以前のタスクに依存させます最初に次のタスクを処理する前。

  2. cachedthreadLocalsはすぐにはGCされません(あるunknown瞬間に-制御不能)それらのキーは WeakReference であるため、知らないうちにOOMが発生する可能性があります。

remove()が明示的に呼び出されずにOOMが発生するような場合の簡単なデモ。

public class ThreadLocalOne {
    private static final int THREAD_POOL_SIZE = 500;
    private static final int LIST_SIZE = 1024 * 25;

    private static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);

        for (int i = 0; i < THREAD_POOL_SIZE; i++) {
            executorService.execute(() -> {
                threadLocal.set(getBigList());
                System.out.println(Thread.currentThread().getName() + " : " + threadLocal.get().size());
                // threadLocal.remove(); 
                // explicitly remove the cache, OOM shall not occur;
            });
        }
        executorService.shutdown();
    }

    private static List<Integer> getBigList() {
        List<Integer> ret = new ArrayList<>();
        for (int i = 0; i < LIST_SIZE; i++) {
            ret.add(i);
        }
        return ret;
    }
}
0
Hearen