Javaのマルチスレッドモジュールでは、ThreadLocalはよく聞かれる知識ポイントです。質問をする方法はたくさんあります。ステップバイステップの場合もあれば、私のトピックのようの場合もあります。したがって、徹底的なものにすぎません。あなたがどのように尋ねても、理解することは、すべてうまくいくことができます。
この記事は主に以下の観点から分析し理解します
1.ThreadLocalとは
2.ThreadLocalの使用方法
3.ThreadLocalソースコード分析
4.ThreadLocalメモリリークの問題
ThreadLocalとは何ですか?
名前から、ThreadLocalはスレッド変数と呼ばれていることがわかります。つまり、ThreadLocalに入力された変数は現在のスレッドに属し、変数は他のスレッドから分離されています。ThreadLocalは、各スレッドに変数のコピーを作成するため、各スレッドは独自の内部コピー変数にアクセスできます。
文字通りの意味からは非常に理解しやすいですが、実際の使用の観点からはそれほど簡単ではありません。インタビューでよく聞かれるポイントとして、使用シナリオも非常に豊富です。
1.オブジェクトをレイヤー間で渡す場合、ThreadLocalを使用すると、複数のパスを回避し、レイヤー間の制約を破ることができます。
2.スレッド間のデータ分離
3.トランザクション操作を実行して、スレッドトランザクション情報を保存します。
4.データベース接続、セッションセッション管理。
ThreadLocalの使い方は?
ThreadLocalの役割はスレッドごとにコピーを作成することなので、例を使用して次のことを確認します。
public static void main(String[] args) {
//新建一个threadLocal
ThreadLocal<String>local = new ThreadLocal<>();
//新建一个随机数
Random random = new Random();
//利用java8的stream新建5个线程
IntStream.range(0,5).forEach(a -> new Thread(() -> {
//为每一个线程设置相应的local值
local.set(a+ " " + random.nextInt(10) );
System.out.println("线程和local值分别是: "+ local.get());
try{
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
}).start() );
}
// 线程和local值分别是: 0 0
// 线程和local值分别是: 3 0
// 线程和local值分别是: 4 2
// 线程和local值分别是: 2 6
// 线程和local值分别是: 1 3
結果から、各スレッドには独自のローカル値があることがわかります。別のスレッドが現在のローカル値を時間内に読み取れるように、スリープ時間を設定します。
これはTheadLocalの基本的な使用法ですが、非常に簡単ですか?では、なぜデータベースに接続するときにもっと使用されるのでしょうか。
データベースを使用するときは、最初にデータベース接続を確立し、不足したら閉じます。これは非常に深刻な問題です。クライアントがデータベースを頻繁に使用する場合は、複数の接続を確立して閉じる必要があります。サーバー圧倒されるかもしれませんが、どうすればよいですか?10,000のクライアントがある場合、サーバーのプレッシャーはさらに大きくなります。
ThreadLocalは現時点で最適です。これは、ThreadLocalが各スレッドで接続のコピーを作成し、スレッド内のどこでも使用でき、スレッドが相互に影響を与えないため、スレッドセーフの問題が発生しないためです。プログラムの実行パフォーマンスに深刻な影響を及ぼします。とても便利ですか?
上記は主に基本的なケースを説明し、データベースに接続するときにThreadLocalが使用される理由を分析するためのものです。ソースコードの観点からThreadLocalの動作原理を分析してみましょう。
ThreadLocalソースコード分析
最初の例では、getメソッドとsetメソッドの2つのメソッドのみが示されていますが、実際には、注意が必要なメソッドがさらにいくつかあります。
-
セットメソッド
/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
setメソッドから、現在のスレッドtが最初に取得され、次にgetMapが呼び出されてThreadLocalMapが取得されることがわかります。マップが存在する場合、現在のスレッドオブジェクトtがキーとして使用され、格納されるオブジェクトは値としてマップに保存されます。マップが存在しない場合は、マップを初期化します。
OK、この時点で、ThreadLocalMapとは何か、getMapメソッドがどのように実装されているかについて疑問があると思います。これらの質問で、見下ろし続けてください。まず、ThreadLocalMapを見てください。
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
ThreadLocalMapは、実際にはThreadLocalの静的内部クラスであり、データを保存するためのエントリを定義し、継承された弱参照でもあることがわかります。Entry内のキーとしてThreadLocalを使用し、設定した値を値として使用します。
getMapもあります
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
現在のスレッドtを呼び出し、現在のスレッドtのメンバー変数threadLocalsを返します。そして、threadLocalsは実際にはThreadLocalMapです。
-
getメソッド
/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
上記のThreadLocalの導入により、このメソッドを十分に理解していると思います。最初に現在のスレッドを取得し、次にgetMapメソッドを呼び出してThreadLocalMapを取得します。マップがnullでない場合は、現在のスレッドをエントリとして使用します。 ThreadLocalMapのキー、次に値対応する値として、そうでない場合は初期値を設定します。
初期値の設定方法は?
/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
-
removeメソッド
は、マップから削除できます。/** * Removes the current thread's value for this thread-local * variable. If this thread-local variable is subsequently * {@linkplain #get read} by the current thread, its value will be * reinitialized by invoking its {@link #initialValue} method, * unless its value is {@linkplain #set set} by the current thread * in the interim. This may result in multiple invocations of the * {@code initialValue} method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
(1)各スレッドはThreadLocalMapへの参照を維持します
(2)ThreadLocalMapはThreadLocalの内部クラスであり、Entryを使用して格納します
(3)ThreadLocalによって作成されたコピーは、独自のThreadLocalMapである独自のthreadLocalsに格納されます。
(4)ThreadLocalMapのキー値はThreadLocalオブジェクトであり、複数のthreadLocal変数が存在する可能性があるため、それらはマップに格納されます。
(5)getを実行する前に、最初に設定する必要があります。そうしないと、nullポインタ例外が報告されます。もちろん、初期化することもできますが、initialValue()メソッドを書き直す必要があります。
(6)ThreadLocal自体は値を格納せず、スレッドがThreadLocalMapから値を取得できるようにするためのキーとして機能するだけです。
ThreadLocalで注意すべき他のいくつかのポイント
ThreadLocalを紹介する記事は、1つのポイント、つまりメモリリークの問題を理解するのに役立ちます。まず、下の写真を見てみましょう。
上の図は、ThreadLocalとThreadおよびThreadLocalMapの関係を詳細に示しています。
1.Threadにマップがあります。これはThreadLocalMapです。
2. ThreadLocalMapのキーはThreadLocalであり、値は自分で設定します。
3. ThreadLocalは弱参照です。nullの場合、ガベージとして扱われます。
4.要点はここにあります。突然、ThreadLocalがnullになり、ガベージコレクターによってリサイクルされます。ただし、現時点では、ThreadLocalMapのライフサイクルはThreadと同じです。リサイクルされません。現時点では、現象です。つまり、ThreadLocalMapのキーはなくなりましたが、値はまだ残っているため、メモリリークが発生します。
解決策:ThreadLocalを使用した後、メモリオーバーフローを回避するために削除操作を実行します。