Java ThreadLocal変数が静的である必要がある理由


101

ここでThreadlocalのJavaDocを読んでいました

https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ThreadLocal.html

また、「ThreadLocalインスタンスは、通常、状態をスレッド(たとえば、ユーザーIDまたはトランザクションID)に関連付けたいクラスのプライベート静的フィールドです。」

しかし、私の質問はなぜ彼らがそれを静的(通常)にすることを選んだのかです-「スレッドごと」の状態を持つことは少し混乱しますが、フィールドは静的ですか?

回答:


131

それがインスタンスレベルのフィールドである場合は、実際には「スレッドごと-インスタンスごと」であり、保証された「スレッドごと」ではないためです。それは通常あなたが探している意味ではありません。

通常は、ユーザーカンバセーションやWebリクエストなどをスコープとするオブジェクトのようなものを保持します。それらをクラスのインスタンスにサブスコープすることもできません。
1つのWebリクエスト=> 1つの持続セッション。
オブジェクトごとに1つのWebリクエスト=> 1つの永続化セッションではありません。


2
ThreadLocalがどのように使用されるかを示しているので、この説明が好きです
kellyfj 2010年

4
インスタンスごとのスレッドは有用なセマンティックスですが、そのパターンのほとんどの使用は非常に多くのオブジェクトを含むため、オブジェクトをThreadLocalスレッドごとのインスタンスにマップするハッシュセットへの参照を保持するのに適しています。
スーパーキャット2014年

@optionalこれは、ThreadLocalそれらのThreadLocalインスタンスが同じスレッドに存在する場合でも、非静的の各インスタンスが独自のスレッドローカルデータを保持することを意味します。それを行うのは必ずしも間違っているわけではありません-それはちょうど2つの中で最も人気のないパターンかもしれないと思います
geg

17

静的にするか、クラスの静的フィールドを回避しようとする場合は、クラス自体をシングルトンにして、グローバルに使用できるシングルトンがある限り、インスタンスレベルのThreadLocalを安全に使用できます。


12

である必要はありません。重要なことは、それがシングルトンであるべきだということです。


3

その理由は、変数はスレッドに関連付けられたポインターを介してアクセスされるためです。これらはスレッドスコープを持つグローバル変数のように機能するため、静的が最も適しています。これは、pthreadなどのスレッドローカル状態を取得する方法であるため、これは単に履歴と実装の偶然である可能性があります。


1

スレッドごとのインスタンスごとにthreadlocalを使用するのは、オブジェクトのすべてのメソッドで何かを表示し、通常のフィールドの場合のようにアクセスを同期せずにスレッドセーフにする場合です。


1

参照してください。これは、これはより良い理解を与えます。

つまり、ThreadLocalオブジェクトはKey-Valueマップのように機能します。スレッドがThreadLocal get/setメソッドを呼び出すと、スレッドオブジェクトがマップのキーに、値がマップの値に取得/格納されます。異なるマップのエントリに存在するため、スレッドごとに異なる値のコピー(ローカルに保存したい)があるのはこのためです。

そのため、すべての値を保持するために必要なマップは1つだけです。必須ではありませんが、複数のマップ(静的を宣言せずに)を使用して、各スレッドオブジェクトを保持することもできます。これは完全に冗長であるため、静的変数が推奨されます。


-1

static final ThreadLocal 変数はスレッドセーフです。

staticThreadLocal変数を、それぞれのスレッドに対してのみ複数のクラスで利用できるようにします。これは、複数のクラスにまたがるそれぞれのスレッドローカル変数の一種のグローバル変数の宣言です。

このスレッドの安全性は、次のコードサンプルで確認できます。

  • CurrentUser -現在のユーザーIDをThreadLocalに保存します
  • TestService-メソッドを使用した単純なサービスgetUser()-CurrentUserから現在のユーザーを取得します。
  • TestThread -このクラスは、複数のスレッドを作成し、ユーザーIDを同時に設定するために使用されます

public class CurrentUser

public class CurrentUser {
private static final ThreadLocal<String> CURRENT = new ThreadLocal<String>();

public static ThreadLocal<String> getCurrent() {
    return CURRENT;
}

public static void setCurrent(String user) {
    CURRENT.set(user);
}

}

public class TestService {

public String getUser() {
    return CurrentUser.getCurrent().get();
}

}

import java.util.ArrayList;
import java.util.List;

public class TestThread {

public static void main(String[] args) {

  List<Integer> integerList = new ArrayList<>();

  //creates a List of 100 integers
  for (int i = 0; i < 100; i++) {

    integerList.add(i);
  }

  //parallel stream to test concurrent thread execution
  integerList.parallelStream().forEach(intValue -> {

    //All concurrent thread will set the user as "intValue"
    CurrentUser.setCurrent("" + intValue);
    //Thread creates a sample instance for TestService class
    TestService testService = new TestService();
    //Print the respective thread name along with "intValue" value and current user. 
    System.out.println("Start-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());

    try {
      //all concurrent thread will wait for 3 seconds
      Thread.sleep(3000l);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    //Print the respective thread name along with "intValue" value and current user.
    System.out.println("End-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());
  });

}

}

TestThreadメインクラスを実行します。出力-

Start-main->62->62
Start-ForkJoinPool.commonPool-worker-2->31->31
Start-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-1->87->87
End-main->62->62
End-ForkJoinPool.commonPool-worker-1->87->87
End-ForkJoinPool.commonPool-worker-2->31->31
End-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-2->32->32
Start-ForkJoinPool.commonPool-worker-3->82->82
Start-ForkJoinPool.commonPool-worker-1->88->88
Start-main->63->63
End-ForkJoinPool.commonPool-worker-1->88->88
End-main->63->63
...

分析のまとめ

  1. 「メイン」スレッドが開始し、現在のユーザーを「62」に設定します。並列に「ForkJoinPool.commonPool-worker-2」スレッドが開始し、現在のユーザーを「31」に設定します。並列に「ForkJoinPool.commonPool-worker-3」スレッドが開始し、現在に設定します"81"としてユーザー、並列に "ForkJoinPool.commonPool-worker-1"スレッドが開始し、現在のユーザーを "87"として設定Start-main-> 62-> 62 Start-ForkJoinPool.commonPool-worker-2-> 31-> 31 Start-ForkJoinPool.commonPool-worker-3-> 81-> 81 Start-ForkJoinPool.commonPool-worker-1-> 87-> 87
  2. 上記のすべてのスレッドは3秒間スリープします
  3. main実行が終了して現在のユーザーが「62」として印刷されます。並列ForkJoinPool.commonPool-worker-1実行が終了して現在のユーザーが「87」として印刷されます。並列ForkJoinPool.commonPool-worker-2実行が終了して現在のユーザーが「31」として印刷されます。並列ForkJoinPool.commonPool-worker-3実行が終了して現在のユーザーが「81」として印刷されます

推論

並行スレッドは、「static final ThreadLocal」として宣言されている場合でも、正しいユーザーIDを取得できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.