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
内のデータへの参照であるため、スレッドプールを使用するアプリケーションサーバーでThreadLocal
sを使用すると、クラスローディングリークが発生する可能性があります。 ThreadLocal
のget()
メソッドを使用して、自分がset()
またはremove()
を使用しているThreadLocal
をクリーンアップするときは、細心の注意を払う必要があります。
完了したときにクリーンアップしないと、デプロイされたWebアプリケーションの一部としてロードされたクラスへの参照は、 永久ヒープ のままになり、ガベージコレクトされることはありません。 Thread
はWebアプリケーションが所有するものではないため、Webアプリケーションを再デプロイ/アンデプロイしても、各Thread
からWebアプリケーションのクラスへの参照はクリーンアップされません。連続して展開するたびに、ガベージコレクションされることのないクラスの新しいインスタンスが作成されます。
Java.lang.OutOfMemoryError: PermGen space
のせいでメモリ不足の例外が発生することがあります。また、いくつかのgooglingの後では、バグを修正するのではなく、単に-XX:MaxPermSize
を増やすことになるでしょう。
これらの問題が発生した場合は、 EclipseのMemory Analyzer または Frank Kievietのガイド および followup を使用して、どのスレッドおよびクラスがこれらの参照を保持しているかを判断できます。
更新:再発見 Alex Vasseurのブログエントリ で、私が抱えていたいくつかのThreadLocal
の問題を突き止めることができました。
多くのフレームワークは、ThreadLocalsを使用して、現在のスレッドに関連するコンテキストを維持します。たとえば、現在のトランザクションがThreadLocalに格納されている場合、スタックの誰かがそれにアクセスする必要がある場合に備えて、すべてのメソッド呼び出しでパラメーターとして渡す必要はありません。 Webアプリケーションは、現在の要求とセッションに関する情報をThreadLocalに保存して、アプリケーションがそれらに簡単にアクセスできるようにします。 Guiceを使用すると、挿入されたオブジェクトに カスタムスコープ を実装するときにThreadLocalsを使用できます(Guiceのデフォルト servletスコープ ほとんどの場合、同様に使用します。
ThreadLocalsはグローバル変数の一種です(1つのスレッドに制限されているため多少悪ではありませんが)。これらを使用するときは、望ましくない副作用やメモリリークを避けるように注意する必要があります。 ThreadLocal値が不要になったときに常に自動的にクリアされ、APIの誤った使用が不可能になるようにAPIを設計します(たとえば、 this )。 ThreadLocalsを使用してコードをクリーンにすることができますが、まれに、それらが何かを機能させる唯一の方法である場合もあります(私の現在のプロジェクトには、このような2つのケースがありました;文書化されています here 「静的フィールドとグローバル変数」の下)。
Javaでは、スレッドごとに異なる可能性のあるデータがある場合、そのデータをそれを必要とする(または必要とする可能性がある)すべてのメソッドに渡すか、またはデータとスレッドを関連付けることを選択します。すべてのメソッドがすでに共通の「コンテキスト」変数を渡す必要がある場合は、どこにでもデータムを渡すことが有効な場合があります。
そうでない場合は、追加のパラメータを使用してメソッドシグネチャを整理したくない場合があります。スレッド化されていない世界では、グローバル変数と同等のJavaで問題を解決できます。スレッド化されたWordでは、グローバル変数と同等のものはスレッドローカル変数です。
Book Java Concurrency in Practiceにはとても良い例があります。作者( Joshua Bloch )がスレッドの安全性を達成するための最も簡単な方法の1つである方法を説明し、ThreadLocalはスレッドの制限を維持するためのより正式な手段です。最後に、彼はまたそれをグローバル変数として使うことによって人々がそれを悪用することができる方法を説明します。
私は上記の本からテキストをコピーしましたが、ThreadLocalをどこで使用すべきかを理解することはそれほど重要ではないため、コード3.10はありません。
スレッドローカル変数は、可変のシングルトンまたはグローバル変数に基づくデザインでの共有を防ぐためによく使用されます。たとえば、シングルスレッドアプリケーションでは、すべてのメソッドにConnectionを渡さなくても済むように、起動時に初期化されるグローバルデータベース接続を維持できます。 JDBC接続はスレッドセーフではない可能性があるため、追加の調整なしでグローバル接続を使用するマルチスレッドアプリケーションもスレッドセーフではありません。リスト3.10のConnectionHolderのように、ThreadLocalを使用してJDBC接続を格納することで、各スレッドは独自の接続を持ちます。
ThreadLocalは、アプリケーションフレームワークの実装に広く使用されています。たとえば、J2EEコンテナはEJB呼び出しの間トランザクションコンテキストを実行スレッドに関連付けます。これは、トランザクションコンテキストを保持する静的なThread-Localを使用して簡単に実装されます。フレームワークコードが現在実行中のトランザクションを特定する必要がある場合、このThreadLocalからトランザクションコンテキストを取得します。これは、実行コンテキスト情報をすべてのメソッドに渡す必要性を減らすという点で便利ですが、このメカニズムを使用するコードはすべてフレームワークに結合されます。
ThreadLocalをグローバル変数を使用するライセンスとして、または「隠し」メソッド引数を作成する手段として扱うことで、ThreadLocalを悪用するのは簡単です。グローバル変数と同様に、スレッドローカル変数は再利用性を損ない、クラス間に隠れたカップリングを導入する可能性があるため、慎重に使用する必要があります。
本質的に、変数の値が現在のスレッドに依存する必要があり、それがあなたにとって便利でない場合他の方法で値をスレッドにアタッチします(たとえば、サブクラス化スレッド)。
典型的なケースは、他のフレームワークがコードを実行しているスレッドを作成した場合です。サーブレットコンテナ、または変数が「論理的な場所」にあるため(Threadサブクラスまたは他のハッシュマップからぶら下がっている変数ではなく)ThreadLocalを使用する方が合理的です。
私のWebサイトには、さらにいくつかの ThreadLocalの使用に関する議論と例 があります。
スレッド番号が必要な特定の並行アルゴリズムの各スレッドに「スレッドID」をアタッチする方法としてThreadLocalを使用することを推奨する人もいます(例:Herlihy&Shavitを参照)。そのような場合は、実際に利益を得ていることを確認してください!
Webアプリケーションサーバーはスレッドプールを保持している可能性があり、クライアントへの応答前にThreadLocal
変数を削除する必要があるため、現在のスレッドは次の要求で再利用される可能性があります。
ドキュメンテーション はそれを非常によく言っている:「(スレッドローカル変数に)(そのgetまたはsetメソッドを介して)アクセスするそれぞれのスレッドはそれ自身の、独立して初期化された変数のコピーを持っている」。
各スレッドがそれ自身の何かのコピーを持たなければならないとき、あなたはそれを使います。デフォルトでは、データはスレッド間で共有されます。
JavaのThreadLocalはJDK 1.2で導入されましたが、後でJDK 1.5でThreadLocal変数の型安全性を導入するために一般化されました。
ThreadLocalはThreadスコープと関連付けることができ、Threadによって実行されるすべてのコードはThreadLocal変数にアクセスできますが、2つのスレッドはお互いにThreadLocal変数を見ることはできません。
各スレッドはThreadLocal変数の排他的コピーを保持します。これは、スレッドの終了後または終了後に、通常または例外により、ガベージコレクションの対象となります。これらのThreadLocal変数に他のライブ参照がない場合。
JavaのThreadLocal変数は通常Classesのプライベート静的フィールドであり、Thread内でその状態を維持します。
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";
}
};
さらに、ThreadLocal
に使用されるクラスのインスタンス化メソッド(コンストラクタ、ファクトリメソッド)がパラメータをとらない場合は、単純にメソッド参照(Java 8で導入されたもの)を使用できます。
class NotThreadSafe {
// no parameters
public NotThreadSafe(){}
}
ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);
注: Java.util.function.Supplier
が呼び出されたが値が以前に評価されていない場合にのみ評価されるThreadLocal#get
lambdaを渡しているため、評価は遅延します。
ThreadLocal
は、異なるスレッド間で共有すべきではない状態を持ちたいが、その寿命全体を通して各スレッドからアクセスできるようにしたい場合に便利です。
例として、各リクエストが異なるスレッドによって処理されるWebアプリケーションを想像してください。リクエストごとに1つのデータが複数回必要になることを想像してみてください。これは計算にかなりの費用がかかります。ただし、そのデータは受信リクエストごとに変更されている可能性があるため、プレーンキャッシュは使用できません。この問題に対する簡単で素早い解決策は、このデータへのアクセスを保持するThreadLocal
変数を持つことです。そのため、各要求に対して一度だけそれを計算する必要があります。もちろん、この問題はThreadLocal
を使わなくても解決できますが、説明のために考案しました。
そうは言っても、ThreadLocal
sは本質的にグローバルな状態の一形態であることに留意してください。結果として、それは他の多くの意味を持ち、他のすべての可能な解決策を考慮した後にのみ使用されるべきです。
ThreadLocalパターンには細心の注意を払う必要があります。 Philが言及したようにいくつかの大きな欠点がありますが、言及されなかったのはThreadLocalコンテキストを設定するコードが「再入可能」ではないことを確認することです。
情報を設定するコードが2回または3回実行されると、悪いことが起こる可能性があります。予想外のときにスレッド上の情報が変化し始めることがあるためです。再度設定する前に、ThreadLocal情報が設定されていないことを確認してください。
いつ?
オブジェクトがスレッドセーフではない場合、スケーラビリティを妨げる同期ではなく、すべてのスレッドに1つのオブジェクトを渡し、それをスレッドスコープ(ThreadLocal)にします。最も頻繁に使用されているがスレッドセーフではないオブジェクトの1つは、データベースConnectionとJMSConnectionです。
どうやって ?
その一例として、Springフレームワークはこれらの接続オブジェクトをThreadLocal変数に保持することで、舞台裏でのトランザクション管理にThreadLocalを多用しています。高レベルでは、トランザクションが開始されると接続を取得し(そして自動コミットを無効にし)、それをThreadLocalに保持します。それ以降のdb呼び出しでは、dbと通信するために同じ接続を使用します。最後に、ThreadLocalから接続を取得し、トランザクションをコミット(またはロールバック)して接続を解放します。
Log4jもMDCを維持するためにThreadLocalを使用していると思います。
@unknown(google)が述べたように、その使用法は参照される値が各スレッド内でユニークになることができるグローバル変数を定義することです。その使用法は通常、現在の実行スレッドにリンクされているある種のコンテキスト情報を格納することを伴います。
Java EEに対応していないクラス(HttpSessionまたはEJB SessionContextにアクセスできないクラス)にユーザーIDを渡すために、Java EE環境でこれを使用します。このように、セキュリティベースの操作にIDを使用するコードは、すべてのメソッド呼び出しで明示的に渡す必要なく、どこからでもIDにアクセスできます。
ほとんどのJava EE呼び出しでの操作の要求/応答サイクルは、ThreadLocalを設定および設定解除するための明確に定義された入り口点と出口点を与えるので、このタイプの使用法を容易にします。
ここで本当に新しいことは何もありませんが、私は今日ThreadLocal
がWebアプリケーションでBean Validationを使うときに非常に役に立つことを発見しました。検証メッセージはローカライズされていますが、デフォルトではLocale.getDefault()
を使用します。 Validator
を別のMessageInterpolator
で設定できますが、Locale
を呼び出すときにvalidate
を指定する方法はありません。それで、静的なThreadLocal<Locale>
(あるいはもっと良いことにThreadLocal
になる必要があるかもしれない他のものを含む一般的なコンテナを作成し、それからあなたのカスタムMessageInterpolator
にLocale
を選ぶことができます。)次のステップこれは、セッション値またはrequest.getLocale()
を使用してロケールを選択し、それをServletFilter
参照に格納するThreadLocal
を記述することです。
ThreadLocalは、非同期メソッド内の複数のスレッドによる可変オブジェクトへのアクセスが確実に同期されるようにします。これは、可変オブジェクトをメソッド内で不変にすることを意味します。
これは、スレッドごとに可変オブジェクトの新しいインスタンスにアクセスしてみることによって実現されます。だから、それは各スレッドへのローカルコピーです。これは、ローカル変数のようにアクセスされるメソッド内でインスタンス変数を作成する際のハックです。メソッドのローカル変数はスレッドでしか使用できないことをご存知のとおり、1つ違いがあります。メソッドローカル変数は、スレッドローカルと共有されている可変オブジェクトがクリーンアップされるまで、複数のメソッドで使用できるようになると、メソッドの実行が終了するとスレッドで使用できなくなります。
定義により:
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()
の有無にかかわらず)。
このラッパーの取得メソッドおよび設定メソッドは、可変オブジェクトではなくスレッドローカルインスタンスで動作します。
Threadlocalのgetter()がThread
の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();
}
}
これでwrapper.getDateFormatter()
はthreadlocal.get()
を呼び出し、それはcurrentThread.threadLocalMap
が this (threadlocal)インスタンスを含んでいるかチェックします。
yesの場合、対応するスレッドローカルインスタンスの値(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値はスレッドオブジェクト自体に格納され、スレッドがガベージコレクションされるとすぐにこれらの値も削除されます。
この link はThreadLocalの使い方をとてもよく説明しています。
【参考】ThreadLocalは共有オブジェクトの更新問題を解決できません。同じスレッド内のすべての操作で共有されるstaticThreadLocalオブジェクトを使用することをお勧めします。 [必須] remove()メソッドは、特にスレッドが頻繁に再利用されるスレッドプールを使用する場合は、ThreadLocal変数で実装する必要があります。そうでないと、後続のビジネスロジックに影響を及ぼし、メモリリークなどの予期しない問題を引き起こす可能性があります。
Javaの ThreadLocal クラスを使用すると、同じスレッドでしか読み書きできない変数を作成できます。したがって、2つのスレッドが同じコードを実行していて、そのコードがThreadLocal変数を参照している場合でも、2つのスレッドは互いの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();
}
}
キャッシング、時にはあなたは同じ値を計算するために多くの時間を計算しなければならないので、メソッドへの入力の最後のセットとその結果を格納することによってコードをスピードアップすることができます。スレッドローカルストレージを使用することで、ロックについて考える必要がなくなります。
マルチスレッドコードでSimpleDateFormatのようなclass helperを使用するための3つのシナリオがあり、最適な使用方法はThreadLocalです
シナリオ
1-=のような使用オブジェクトの共有の助けによりロックまたは同期アプリを作成するメカニズム遅い
2-として使用ローカルオブジェクトメソッド内
このシナリオでは、4 thread each メソッド1000を呼び出す timeがあれば、
40 SimpleDateFormat object createdそしてGCがそれらを消去するのを待っています
-使用ThreadLocal
4つのスレッドがあり、各スレッドに1つのSimpleDateFormatインスタンスを指定した場合
SimpleDateFormatの4スレッド、4オブジェクトです。
ロック機構とオブジェクトの作成と破棄の必要はありません。 (時間の複雑さとスペースの複雑さ)
Threadlocalは、ゼロコストでオブジェクトの再利用性を実現するための非常に簡単な方法を提供します。
更新通知ごとに、複数のスレッドが可変キャッシュのイメージを作成していました。
各スレッドでThreadlocalを使用した後、各スレッドは古いイメージをリセットし、各更新通知でキャッシュから再度更新するだけで済みます。
オブジェクトプールからの通常の再利用可能なオブジェクトには、それらに関連したスレッドセーフティコストがありますが、このアプローチにはありません。