今回は重要なツールであるThreadLocalを紹介します。メモリリークが発生するシナリオ、メモリリークの再現方法、メモリリークを回避するための正しい使い方についても紹介します。
- スレッドローカルとは何ですか? その用途は何ですか?
- スレッドローカルの使用方法
- スレッドローカルの原則
- ThreadLocal を使用する際の落とし穴や注意事項は何ですか?
1. ThreadLocal とは何ですか? その用途は何ですか?
まず、Thread クラスに属性 threadLocals を導入します。
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Thread には、メンバー変数 threadLocals を設定およびアクセスするためのメソッドが提供されていないことがわかりました。では、各スレッドのインスタンス threadLocals パラメーターをどのように操作すればよいでしょうか? このとき、私たちの主人公である ThreadLocal が登場します。
要約すると、ThreadLocal はスレッドThreadのthreadLocals属性のマネージャーです。
つまり、ThreadLocal の get、set、remove 操作の結果は、すべて現在のスレッド Thread インスタンスに対する threadLocals の保存、アクセス、および削除の操作になります。開発者のタスクと同様に、プロダクト マネージャーはタスクを制御できず、プロダクト マネージャーはテクニカル リーダーを通じてのみ開発者にタスクを割り当てることができます。ここでは、それらの間の関係をさらに説明する別の例を示します。つまり、ThreadLocal の get、set、remove 操作の結果は、すべて、現在のスレッド Thread インスタンスに対する threadLocals の保存、アクセス、および削除の操作です。開発者のタスクと同様に、プロダクト マネージャーはタスクを制御できず、プロダクト マネージャーはテクニカル リーダーを通じてのみ開発者にタスクを割り当てることができます。これらの関係をさらに詳しく説明する別の例を次に示します。
- 誰もが銀行カードを持っています
- 誰もが各カードに一定の残高を持っています。
- 銀行カードの残高を取得するには、誰もが銀行の管理システムを経由する必要があります。
- 誰もが自分のカードが保有する残高情報のみを取得でき、他人がアクセスすることはできません。
言いたいThreadLocalへのマップ
- カードはスレッドに似ています
- カード残高属性、カード番号属性などは、Treadlocal の内部属性コレクション threadLocals と同様です
- CardManager は ThreadLocal 管理クラスに似ています
では、ThreadLocal のアプリケーション シナリオは何でしょうか?
実際、私たちは、ThreadLocal が提供する利便性を常にうっかり使用してきました。複数のデータ ソース間の切り替えに慣れていない場合は、Spring が提供する宣言型トランザクションは非常によく知られています。研究開発中は常にこれを使用しています。プロセスとスプリング 宣言型トランザクションの重要な実装基盤は ThreadLocal ですが、Spring 宣言型トランザクションの実装メカニズムを詳しく掘り下げた人は誰もいません。春宣言取引の原理や実装の仕組みについては、また機会があれば紹介したいと思います。
ThreadLocal は非常に強力であることが判明しましたが、アプリケーション開発者はほとんど使用していません。同時に、一部の開発者はメモリ リークやその他の潜在的な問題のため、ThreadLocal を試すことを恐れています。これが ThreadLocal に関する最大の誤解ではないかと思います。後で注意深く分析しますが、方法、方法、方法を正しく使用する限り、問題はありません。ThreadLocal に問題がある場合、プログラムに対する最大の潜在的な危険は Spring の宣言型トランザクションではないでしょうか?
2.ThreadLocalの使い方
より直感的なエクスペリエンスを実現するために、
ThreadLocal
次のシナリオを想定します。
- スレッドごとに ID を生成します。
- 一度設定すると、スレッドのライフサイクル中に変更することはできません。
- コンテナーのアクティビティ中に重複 ID を生成することはできません
ThreadLocal 管理クラスを作成します。
テストプログラムは以下の通りです。同じスレッドを使用して継続的に取得し、IDが変化するかどうかをテストし、テストが完了したらリリースします。
メインプログラムでは複数のスレッドを起動して、ブロックされたスレッド間に影響がないかテストします。
当然のことながら、結果は次のようになります。
結果: 確かに、異なるスレッド間では ID が異なりますが、同じスレッドの ID は同じです。
3.スレッドローカル原則
①ThreadLocalクラスの構造とメソッドの分析:
上の図からわかるように、ThreadLocal
3 つのメソッド get、set、remove、および内部クラス `ThreadLocalMap
②ThreadLocalとThreadの関係:
この図から、Thread の属性 threadLocals が直感的にわかります。特別なマップとして、そのキー値はインスタンスでありThreadLocal
、値の値は設定した値です。
③ThreadLocal操作プロセス:
get メソッドを例として見てみましょう。
このうち、getMap(t) は、以下に示すように、現在のスレッドのスレッドローカルを返し、現在の ThreadLocal インスタンス オブジェクトをキーとして使用して、ThreadLocalMap の値を取得します。初めて使用する場合は、setInitialValue を呼び出します。 ()
設定プロセスも同様です。
注:同じパッケージ内でThread に直接アクセスしてプロパティを宣言することもできるため、ThreadLocal
これは直接実行できます。 t.threadLocals
Thread
ThreadLocal
ThreadLocal.ThreadLocalMap threadLocals = null;
4. ThreadLocal を使用する際の落とし穴と注意事項は何ですか?
ThreadLocal がメモリ リークを引き起こすという恐ろしい見出しをインターネットでよく見かけますが、そのため、最初から ThreadLocal を十分に理解していない開発者は、むやみやたらに使用しようとはしません。使用頻度が低いほど、奇妙になります。これにより、より良い実装ソリューションを逃すことになるため、新しいテクノロジーを敢えて導入し、落とし穴に足を踏み入れることによってのみ、進歩を続けることができます。
ThreadLocal がメモリ リークを引き起こす理由を見てみましょう。どのようなシナリオでメモリ リークが発生する可能性がありますか?
まず、メモリ リークとは何か、また、それに対応するメモリ オーバーフローとは何かを確認しましょう。
① メモリ オーバーフロー: メモリ オーバーフロー。申請者が使用できる十分なメモリがありません。
②メモリリーク:メモリリーク プログラムがメモリを確保した後、確保したメモリ空間を解放できず、メモリリークが蓄積すると最終的にメモリオーバーフローを引き起こします。
どうやらTreadLocalの不正使用によりメモリが解放されなかった模様。
赤枠内には WeakReference という特別なクラスが表示されますが、このクラスもアプリケーション開発者によってはほとんど使用されないので、ここで簡単に紹介しましょう。
WeakReference は次の gc でリサイクルされようとしているのに、なぜプログラムに問題がないのでしょうか?
①そこで、弱い参照のリサイクルメカニズムをテストします。
この種の強参照はリサイクルされません。
ここでの強い参照はリサイクルされません。
上記は弱い参照のリサイクルを示していますが、ThreadLocal での弱い参照のリサイクルを見てみましょう。
②弱ThreadLocal
参照リサイクル状況
上の図に示すように、キーとして ThreadLocal オブジェクトへの外部強参照はありません。次の gc は、キー値が null のデータを確実に生成します。スレッドが時間内に終了しない場合、強参照チェーン Threadref– >Thread–>ThreadLocalMap– .
>Entry が必然的に発生するため、メモリ リークが発生します。
以下では、ThreadLocal によって引き起こされるメモリ リークをシミュレートして再現します。
1. 効果を高めるために、treadlocals のストレージ値を 10,000 個の文字列のリストに設定します。
class ThreadLocalMemory {
// Thread local variable containing each thread's ID
public ThreadLocal<List<Object>> threadId = new ThreadLocal<List<Object>>() {
@Override
protected List<Object> initialValue() {
List<Object> list = new ArrayList<Object>();
for (int i = 0; i < 10000; i++) {
list.add(String.valueOf(i));
}
return list;
}
};
// Returns the current thread's unique ID, assigning it if necessary
public List<Object> get() {
return threadId.get();
}
// remove currentid
public void remove() {
threadId.remove();
}
}
テストコードは次のとおりです。
public static void main(String[] args)
throws InterruptedException {
// 为了复现key被回收的场景,我们使用临时变量
ThreadLocalMemory memeory = new ThreadLocalMemory();
// 调用
incrementSameThreadId(memeory);
System.out.println("GC前:key:" + memeory.threadId);
System.out.println("GC前:value-size:" + refelectThreadLocals(Thread.currentThread()));
// 设置为null,调用gc并不一定触发垃圾回收,但是可以通过java提供的一些工具进行手工触发gc回收。
memeory.threadId = null;
System.gc();
System.out.println("GC后:key:" + memeory.threadId);
System.out.println("GC后:value-size:" + refelectThreadLocals(Thread.currentThread()));
// 模拟线程一直运行
while (true) {
}
}
この時点でメモリ内にメモリ リークがあることをどのようにして知ることができるのでしょうか?
jdk が提供するいくつかのコマンドを使用して、現在のヒープ メモリをダンプできます。コマンドは次のとおりです。jmap -dump:live,format=b,file=heap.bin <pid>
次に、MAT 視覚分析ツールを使用してメモリを表示し、オブジェクト インスタンスの生存ステータスを分析します。
まずツールを開いてメモリ リーク分析を促します。
ここで判断できるのは、ThreadLocalMap インスタンスの Entry.value がリサイクルされていないということです。
最後に、Entry.key がまだ存在するかどうかを確認する必要があります。Dominator ツリーを開き、ThreadLocalMemory を検索すると、生き残っているインスタンスがないことがわかります。
上記では、ThreadLocal の不適切な使用によって引き起こされるメモリ リークを再現しました。デモはこちらです。
そこで、ThreadLocal を使用する場合のメモリ リークの前提条件をまとめました。
① ThreadLocal 参照は null に設定され、後で設定、取得、または削除の操作は行われません。
②スレッドは止まらずに走り続けます。(スレッドプール)
③ガベージコレクションがトリガーされます。(マイナー GC またはフル GC)
ThreadLocal でのメモリ リークの条件は依然として非常に厳しいことがわかります。そのため、メモリ リークを回避するには条件の 1 つを破棄するだけで済みますが、この状況の発生をより適切に回避するために、条件を遵守します。 ThreadLocal を使用するときのルールには次の 2 つの小さな原則があります。
①ThreadLocal は private static Final として宣言されます。
Private および Final は、他のユーザーが変更参照を変更できないようにします。
静的はクラス属性として表現され、プログラムの終了時にのみリサイクルされます。
②ThreadLocalを使用した後は必ずremoveメソッドを呼び出してください。
最も簡単で効果的な方法は、使用後に取り除くことです。