ThreadLocal 変数はいつ、どのように使用すればよいですか?
-
03-07-2019 - |
質問
いつ使用すればよいですか ThreadLocal
変数?
どのように使用されますか?
解決
1つの可能な(そして一般的な)使用法は、スレッドセーフではないオブジェクトがあるが、そのオブジェクトへのアクセスの同期(私はあなたを見ています、 SimpleDateFormat )。代わりに、各スレッドにオブジェクトの独自のインスタンスを与えます。
例:
public class Foo
{
// SimpleDateFormat is not thread-safe, so give one to each thread
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue()
{
return new SimpleDateFormat("yyyyMMdd HHmm");
}
};
public String formatIt(Date date)
{
return formatter.get().format(date);
}
}
他のヒント
ThreadLocal
は特定のThread
内のデータへの参照であるため、スレッドプールを使用するアプリケーションサーバーでget()
を使用すると、クラスローディングリークが発生する可能性があります。 set()
のremove()
メソッドを使用して、java.lang.OutOfMemoryError: PermGen space
または-XX:MaxPermSize
をクリーンアップする際には、十分に注意する必要があります。
完了時にクリーンアップしない場合、デプロイされたwebappの一部としてロードされたクラスへの参照は、パーマネントヒープガベージコレクションを取得します。 <=>はWebアプリが所有するものではないため、Webアプリを再デプロイ/アンデプロイしても、各<=>のWebアプリのクラスへの参照はクリーンアップされません。連続してデプロイするたびに、クラスの新しいインスタンスが作成されますが、ガベージコレクションは行われません。
<=>によりメモリ不足の例外が発生し、バグを修正する代わりにグーグルで<=>を増加させるだけです。
これらの問題が発生した場合、を使用して、これらの参照を保持しているスレッドとクラスを特定できます。 EclipseのMemory Analyzer および/または Frank Kievietのガイドおよびフォローアップ。
更新:再発見 Alex Vasseurのブログエントリ私が抱えていた<=>問題を追跡するのに役立ちました。
多くのフレームワークは、ThreadLocalsを使用して、現在のスレッドに関連するコンテキストを維持します。たとえば、現在のトランザクションがThreadLocalに格納されている場合、スタックの誰かがそれにアクセスする必要がある場合に備えて、すべてのメソッド呼び出しでパラメーターとして渡す必要はありません。 Webアプリケーションは、現在の要求とセッションに関する情報をThreadLocalに保存して、アプリケーションがそれらに簡単にアクセスできるようにします。 Guiceを使用すると、注入されたオブジェクトにカスタムスコープを実装するときにThreadLocalsを使用できます(Guiceのデフォルト< a href = "https://github.com/google/guice/wiki/Scopes" rel = "nofollow noreferrer">サーブレットスコープもおそらく使用します。
ThreadLocalsは一種のグローバル変数です(1つのスレッドに制限されているため、多少悪ではありませんが)。これらを使用するときは、望ましくない副作用やメモリリークを避けるよう注意してください。 ThreadLocal値が不要になったときに常に自動的にクリアされ、APIの誤った使用が不可能になるようにAPIを設計します(たとえば、このような)。 ThreadLocalsを使用してコードをクリーンにすることができます。まれに、何かを機能させる唯一の方法です(私の現在のプロジェクトにはこのような2つのケースがあります。それらはここの<!> quot;静的フィールドとグローバル変数<!> quot;)。
Javaでは、スレッドごとに異なるデータムがある場合、そのデータムを必要とする(または必要とする)すべてのメソッドにそのデータムを渡すか、データムをスレッドに関連付けるかを選択します。すべてのメソッドがすでに共通の<!> quot; context <!> quot;変数。
そうでない場合は、追加のパラメーターを使用してメソッドシグネチャを混乱させたくない場合があります。スレッド化されていない世界では、グローバル変数に相当するJavaの問題を解決できます。スレッド化された単語では、グローバル変数に相当するものはスレッドローカル変数です。
書籍 Javaの並行性の実践には非常に良い例があります。著者( Joshua Bloch )は、スレッドの閉じ込めがスレッドの安全性を達成する最も簡単な方法の1つである方法を説明しています ThreadLocal は、スレッドの閉じ込めを維持するためのより正式な手段です。最後に、グローバル変数として使用することで、人々がどのように悪用できるかについても説明します。
前述の本からテキストをコピーしましたが、ThreadLocalの使用場所を理解することはそれほど重要ではないため、コード3.10が欠落しています。
スレッドローカル変数は、可変シングルトンまたはグローバル変数に基づいたデザインでの共有を防ぐためによく使用されます。たとえば、シングルスレッドアプリケーションは、起動時に初期化されるグローバルデータベース接続を維持し、すべてのメソッドに接続を渡す必要を回避します。 JDBC接続はスレッドセーフではない場合があるため、追加の調整なしでグローバル接続を使用するマルチスレッドアプリケーションもスレッドセーフではありません。リスト3.10のConnectionHolderのように、ThreadLocalを使用してJDBC接続を格納することにより、各スレッドは独自の接続を持ちます。
ThreadLocalは、アプリケーションフレームワークの実装に広く使用されています。たとえば、J2EEコンテナは、EJB呼び出しの間、トランザクションコンテキストを実行中のスレッドに関連付けます。これは、トランザクションコンテキストを保持する静的なスレッドローカルを使用して簡単に実装できます。フレームワークコードが現在実行中のトランザクションを判断する必要がある場合、このスレッドローカルからトランザクションコンテキストをフェッチします。これは、実行コンテキスト情報をすべてのメソッドに渡す必要性を減らしますが、このメカニズムを使用するコードをフレームワークに結合するという点で便利です。
スレッド制限プロパティをグローバル変数を使用するライセンスとして、または<!>#8220; hidden <!>#8221;を作成する手段として扱うことにより、ThreadLocalを簡単に悪用できます。メソッドの引数。グローバル変数と同様に、スレッドローカル変数は再利用性を損ない、クラス間に隠された結合を導入する可能性があるため、注意して使用する必要があります。
本質的に、現在のスレッドに依存する変数の値が必要な場合、他の方法でスレッドに値を添付するのは不便です (たとえば、サブクラス化スレッド)。
典型的なケースは、コードが実行されている他のフレームワークがスレッドを作成した場合です。サーブレットコンテナ、または変数が<!> quot;その論理的な場所<!> quotであるため、ThreadLocalを使用するほうが理にかなっている場合。 (Threadサブクラスまたは他のハッシュマップからぶら下がっている変数ではなく)。
私のウェブサイトには、議論とThreadLocalを使用する場合の例がいくつかあります興味深いかもしれません。
<!> quot;スレッドID <!> quot;を付加する方法としてThreadLocalを使用することを推奨する人もいます。スレッド番号が必要な特定の並行アルゴリズムの各スレッド(例:Herlihy <!> amp; Shavitを参照)。そのような場合、あなたが本当に利益を得ていることを確認してください!
ドキュメントは非常にうまく言っています:<!> quot; [スレッドローカル変数]に(getまたはsetメソッドを介して)アクセスする各スレッドには、独自に初期化された変数のコピーがあります<!> quot;。
各スレッドが何かの独自のコピーを保持する必要がある場合に使用します。デフォルトでは、データはスレッド間で共有されます。
Webappサーバーはスレッドプールを保持する場合があり、クライアントへの応答前にThreadLocal
varを削除する必要があります。したがって、現在のスレッドは次のリクエストで再利用できます。
-
JavaのThreadLocalはJDK 1.2で導入されましたが、後でThreadLocal変数の型安全性を導入するためにJDK 1.5で生成されました。
-
ThreadLocalはThreadスコープに関連付けることができます。Threadによって実行されるすべてのコードはThreadLocal変数にアクセスできますが、2つのスレッドは互いのThreadLocal変数を参照できません。
-
各スレッドはThreadLocal変数の排他的コピーを保持しており、通常または例外により、スレッドの終了または終了後にガベージコレクションの対象となります。これらのThreadLocal変数には他のライブ参照はありません。
-
JavaのThreadLocal変数は通常、クラスのプライベート静的フィールドであり、スレッド内で状態を維持します。
threadlocal変数を使用できる2つの使用例-
1-状態をスレッドに関連付ける必要がある場合(たとえば、ユーザーIDまたはトランザクションID)。これは通常、サーブレットへのすべてのリクエストに一意のtransactionIDが関連付けられているWebアプリケーションで発生します。
// This class will provide a thread local variable which
// will provide a unique ID for each thread
class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
ここで、メソッドwithInitialはラムダ式を使用して実装されていることに注意してください。
2-もう1つのユースケースは、スレッドセーフインスタンスが必要で、同期のパフォーマンスコストが高いため、同期を使用したくない場合です。そのようなケースの1つは、SimpleDateFormatが使用される場合です。 SimpleDateFormatはスレッドセーフではないため、スレッドセーフにするメカニズムを提供する必要があります。
public class ThreadLocalDemo1 implements Runnable {
// threadlocal variable is created
private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue(){
System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() );
return new SimpleDateFormat("dd/MM/yyyy");
}
};
public static void main(String[] args) {
ThreadLocalDemo1 td = new ThreadLocalDemo1();
// Two threads are created
Thread t1 = new Thread(td, "Thread-1");
Thread t2 = new Thread(td, "Thread-2");
t1.start();
t2.start();
}
@Override
public void run() {
System.out.println("Thread run execution started for " + Thread.currentThread().getName());
System.out.println("Date formatter pattern is " + dateFormat.get().toPattern());
System.out.println("Formatted date is " + dateFormat.get().format(new Date()));
}
}
Java 8リリース以降、ThreadLocal
を初期化するためのより宣言的な方法があります:
ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");
Java 8リリースまでは、次を実行する必要がありました。
ThreadLocal<String> local = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "init value";
}
};
さらに、java.util.function.Supplier
に使用されるクラスのインスタンス化メソッド(コンストラクター、ファクトリーメソッド)がパラメーターを取らない場合は、メソッド参照(Java 8で導入)を使用できます:
class NotThreadSafe {
// no parameters
public NotThreadSafe(){}
}
ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);
注:
ThreadLocal#get
が呼び出され、値が以前に評価されなかった場合にのみ評価される<=>ラムダを渡すため、評価は遅延します。
ThreadLocalパターンには非常に注意する必要があります。 Philが言及したようないくつかの大きな欠点がありますが、言及されなかったのは、ThreadLocalコンテキストを設定するコードが<!> quot; re-entrant。<!> quot;
情報を設定するコードが2回目または3回目に実行されると、予期しないときにスレッドの情報が変化し始める可能性があるため、悪いことが起こる可能性があります。再度設定する前に、ThreadLocal情報が設定されていないことを確認してください。
いつ?
スケーラビリティが低下する同期の代わりに、オブジェクトがスレッドセーフではない場合、すべてのスレッドに1つのオブジェクトを与え、ThreadLocalであるスレッドスコープを保持します。最もよく使用されるがスレッドセーフではないオブジェクトの1つは、データベース接続とJMSConnectionです。
方法?
1つの例として、Springフレームワークは、これらの接続オブジェクトをThreadLocal変数に保持することで、舞台裏でトランザクションを管理するためにThreadLocalを多用しています。高レベルでは、トランザクションが開始されると、接続を取得し(そして自動コミットを無効にし)、それをThreadLocalに保持します。さらにデータベースを呼び出すと、同じ接続を使用してデータベースと通信します。最後に、ThreadLocalから接続を取得し、トランザクションをコミット(またはロールバック)して、接続を解放します。
log4jもMDCのメンテナンスにThreadLocalを使用すると思います。
ThreadLocal
は、異なるスレッド間で共有されるべきではない状態が必要な場合に役立ちますが、その有効期間全体を通して各スレッドからアクセスできる必要があります。
例として、各アプリケーションが異なるスレッドによって処理されるWebアプリケーションを想像してください。リクエストごとに、データの一部が複数回必要になることを想像してください。これは、計算が非常に高価です。ただし、そのデータは着信要求ごとに変更される可能性があるため、プレーンキャッシュを使用できません。この問題の簡単で迅速な解決策は、このデータへのアクセスを保持する<=>変数を用意することです。これにより、リクエストごとに1回だけ計算する必要があります。もちろん、この問題は<=>を使用しなくても解決できますが、説明のために考案しました。
それは言ったが、<=>は本質的にグローバルな状態の一形態であることに留意してください。その結果、他の多くの意味を持ち、他のすべての可能なソリューションを検討した後にのみ使用する必要があります。
ここでは特に新しいことはありませんが、今日、WebアプリケーションでBean Validationを使用するときにThreadLocal
が非常に役立つことを発見しました。検証メッセージはローカライズされていますが、デフォルトではLocale.getDefault()
を使用します。別のValidator
でMessageInterpolator
を構成できますが、Locale
を呼び出すときにvalidate
を指定する方法はありません。したがって、静的なThreadLocal<Locale>
を作成することができます(または、さらに良いのは、ServletFilter
である必要があるかもしれない他のものを含む一般的なコンテナーで、カスタムrequest.getLocale()
から<=>を選択させることです。次のステップは、 <=>セッション値を使用するか、<=>を使用してロケールを選択し、<=>参照に保存します。
@unknown(google)によって言及されたように、その使用法は、参照される値が各スレッドで一意になるグローバル変数を定義することです。通常、この使用法では、現在の実行スレッドにリンクされている何らかのコンテキスト情報を保存する必要があります。
Java EE環境でこれを使用して、Java EEに対応していないクラスにユーザーIDを渡します(HttpSessionまたはEJB SessionContextにアクセスできません)。このようにして、セキュリティベースの操作でIDを使用するコードは、すべてのメソッド呼び出しで明示的に渡す必要なく、どこからでもIDにアクセスできます。
ほとんどのJava EE呼び出しの操作の要求/応答サイクルは、ThreadLocalを設定および設定解除するための明確に定義されたエントリポイントと出口ポイントを提供するため、このタイプの使用を簡単にします。
ThreadLocalは、複数のオブジェクトによる可変オブジェクトへのアクセスを保証します 非同期メソッドのスレッドは同期されます。 メソッド内で不変である可変オブジェクト。
これ スレッドごとに可変オブジェクトの新しいインスタンスを与えることで実現されます アクセスしてみてください。したがって、各スレッドへのローカルコピーです。これはいくつかです 次のようにアクセスされるメソッドでインスタンス変数を作成するハック ローカル変数。あなたが知っているように、メソッドローカル変数は利用可能です スレッドとの違いは、1つです。メソッドのローカル変数は メソッドの実行が変更可能な場所で終了すると、スレッドで利用可能 threadlocalと共有されているオブジェクトは、複数の クリーンアップするまでのメソッド。
定義別:
JavaのThreadLocalクラスを使用すると、次のことができる変数を作成できます。 同じスレッドによってのみ読み取りと書き込みが行われます。したがって、たとえ2つのスレッドが 同じコードを実行しており、コードにはへの参照があります ThreadLocal変数、2つのスレッドはお互いの ThreadLocal変数。
Javaの各Thread
にはThreadLocalMap
が含まれています。
どこ
Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.
ThreadLocalの実現:
ここで、以下のような可変オブジェクトを保持するThreadLocalのラッパークラスを作成します(initialValue()
の有無にかかわらず)。
このラッパーのgetterおよびsetterは、可変オブジェクトではなくthreadlocalインスタンスで動作するようになりました。
threadlocalのgetter()がwrapper.getDateFormatter()
のthreadlocalmapで値を見つけられなかった場合。次に、initialValue()を呼び出して、スレッドに関するプライベートコピーを取得します。
class SimpleDateFormatInstancePerThread {
private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd") {
UUID id = UUID.randomUUID();
@Override
public String toString() {
return id.toString();
};
};
System.out.println("Creating SimpleDateFormat instance " + dateFormat +" for Thread : " + Thread.currentThread().getName());
return dateFormat;
}
};
/*
* Every time there is a call for DateFormat, ThreadLocal will return calling
* Thread's copy of SimpleDateFormat
*/
public static DateFormat getDateFormatter() {
return dateFormatHolder.get();
}
public static void cleanup() {
dateFormatHolder.remove();
}
}
Now threadlocal.get()
はcurrentThread.threadLocalMap
を呼び出し、<=>に this (threadlocal)インスタンスが含まれていることを確認します。
yesの場合、対応するthreadlocalインスタンスの値(SimpleDateFormat)を返します
それ以外の場合、このスレッドローカルインスタンスinitialValue()でマップを追加します。
これにより、この可変クラスでスレッドセーフが実現されます。各スレッドは、独自の可変インスタンスを使用していますが、同じThreadLocalインスタンスを使用しています。意味すべてのスレッドは、キーとして同じThreadLocalインスタンスを共有しますが、値として異なるSimpleDateFormatインスタンスを共有します。
https://github.com/skanagavelu/yt。 tech / blob / master / src / ThreadLocalTest.java
スレッドローカル変数は、多くの場合、設計に基づいて共有を防ぐために使用されます 可変シングルトンまたはグローバル変数。
接続プールを使用していないときに、スレッドごとに個別のJDBC接続を確立するなどのシナリオで使用できます。
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
getConnectionを呼び出すと、そのスレッドに関連付けられている接続が返されます。同じことは、スレッド間で共有したくないdateformat、トランザクションコンテキストなどの他のプロパティでも実行できます。
同じためにローカル変数を使用することもできますが、これらのリソースは通常作成に時間がかかるため、ビジネスロジックを実行するたびに何度も作成する必要はありません。ただし、ThreadLocal値はスレッドオブジェクト自体に格納され、スレッドがガベージコレクションされるとすぐに、これらの値も失われます。
このリンクは、ThreadLocalの使用方法を非常によく説明しています。
の スレッドローカル Java の class を使用すると、同じスレッドでのみ読み書きできる変数を作成できます。したがって、2 つのスレッドが同じコードを実行していて、そのコードに ThreadLocal 変数への参照がある場合でも、2 つのスレッドは互いの ThreadLocal 変数を認識できません。
[参照用] ThreadLocalは、共有オブジェクトの更新の問題を解決できません。同じスレッド内のすべての操作で共有されるstaticThreadLocalオブジェクトを使用することをお勧めします。 [必須] remove()メソッドは、特にスレッドが頻繁に再利用されるスレッドプールを使用する場合、ThreadLocal変数によって実装する必要があります。そうしないと、後続のビジネスロジックに影響を及ぼし、メモリリークなどの予期しない問題を引き起こす可能性があります。
キャッシング、時には同じ値を何度も計算する必要があるため、メソッドと結果への最後の入力セットを保存することで、コードを高速化できます。スレッドローカルストレージを使用すると、ロックについて考える必要がなくなります。
ThreadLocalは、JVMによって特別にプロビジョニングされた機能で、スレッド専用の分離されたストレージスペースを提供します。インスタンススコープ変数の値がクラスの特定のインスタンスのみにバインドされているように。各オブジェクトには唯一の値があり、互いの値を見ることができません。 ThreadLocal変数の概念もそうであり、それらはオブジェクトインスタンスの意味でスレッドに対してローカルであり、それを作成したスレッドを除く他のスレッドはそれを見ることができません。 こちら
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(1000);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
public static void main(String[] args) {
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
}
}
Threadlocalは、オブジェクトの再利用性をゼロコストで実現する非常に簡単な方法を提供します。
更新通知ごとに、複数のスレッドが可変キャッシュのイメージを作成している状況がありました。
各スレッドでThreadlocalを使用すると、各スレッドは古いイメージをリセットし、更新通知ごとにキャッシュから再度更新するだけで済みます。
オブジェクトプールの通常の再利用可能なオブジェクトには、スレッドセーフティコストが関連付けられていますが、このアプローチには何もありません。
マルチスレッドコードでSimpleDateFormatのようなクラスヘルパーを使用するための3つのシナリオがあります。最良のシナリオは ThreadLocal
を使用することですシナリオ
1-アプリを低速にするロックまたは同期メカニズムの助けを借りて、共有オブジェクトのように使用する
2-メソッド内でローカルオブジェクトとして使用
このシナリオでは、 4つのスレッドがある場合、それぞれがメソッドを1000回呼び出すと、
4000 SimpleDateFormat オブジェクトが作成され、GCがそれらを消去するのを待っている
3- ThreadLocal の使用
4つのスレッドがあり、各スレッドに1つのSimpleDateFormatインスタンスを与えた場合
そのため、SimpleDateFormatの 4スレッド、 4オブジェクトがあります。
ロックメカニズムやオブジェクトの作成と破棄の必要はありません。 (時間の複雑さとスペースの複雑さ)