まだThreadLocalを理解していませんか?それはあなたがまだこの記事を読んでいないからです

Synchronizedと比較して、その機能は何ですか

ThreadLocalとSynchronizedの両方を使用して、マルチスレッドの同時アクセスを解決します。しかし、ThreadLocalとSynchronizedは根本的に異なります。同期とは、ロックメカニズムを使用して、変数またはコードブロックに特定の時間に1つのスレッドからのみアクセスできるようにすることです。

名前から、ThreadLocalはスレッド変数と呼ばれていることがわかります。つまり、ThreadLocalに入力された変数は現在のスレッドに属し、変数は他のスレッドから分離されています。ThreadLocalは、各スレッドに変数のコピーを作成するため、各スレッドは独自の内部コピー変数にアクセスできます。

文字通りの意味からは非常に理解しやすいですが、実際の使用の観点からはそれほど簡単ではありません。インタビューでよくある質問として、使用シナリオも非常に豊富です。

  1. レベル間でオブジェクトを転送する場合、ThreadLocalを使用すると、複数の転送を回避し、レベル間の制約を破ることができます。
  2. スレッド間の階層的な分離。
  3. トランザクション操作を実行して、スレッドトランザクション情報を格納します。
  4. データベース接続、セッションセッション管理。

これで、ThreadLocalの一般的な理解が得られました。使い方を見てみましょう。

ThreadLocalの使用方法

ThreadLocalの役割はスレッドごとにコピーを作成することなので、例を使用して次のことを確認しましょう。

 

 

結果から、各スレッドには独自のローカル値があることがわかります。言い換えると、threadLocalの値は、スレッドとスレッドの分離です。特定の原則は、ThreadLocalMapがさまざまなスレッドにデータを格納する方法を次のように描くことができます。

ThreadLocalを初めて学ぶ場合、友達が混乱する可能性があります。ThreadLocalを理解していませんでした。ThreadLocalMapについて教えてください。心配しないでください、私たちは見下し続けます。

 

ThreadLocal-データベース接続の使用シナリオ

最も批判されているデータベース接続プールは、接続の作成と終了です。これには多くのリソースと時間が必要です。ThreadLocalは、この問題の解決にも役立ちます。

 

これはデータベース接続管理クラスです。データベースを使用する場合、最初のステップはデータベース接続を確立することです。使い切ったら閉じます。これには非常に深刻な問題があります。ユーザーがデータベースを頻繁に使用する場合は、複数の接続とシャットダウンを確立する必要があります。このように、私たちのサーバーは圧倒される可能性があります、それで私たちは何をすべきですか?10,000のクライアントがある場合、サーバーのプレッシャーはさらに大きくなります。

現時点では、ThreadLocalを使用するのが最善です。ThreadLocalは各スレッドにコピーを作成するためです。また、スレッド内のどこでも使用できます。スレッドは相互に影響を与えません。このように、スレッドセーフの問題はなく、プログラムのパフォーマンスに深刻な影響を与えることはなく、接続の頻繁な作成と破棄を回避できます。もちろん、実際には、処理するデータベース接続プールがありますが、接続オブジェクトの頻繁な作成と破棄回避するために、目的は非常に明確です!

上記は主に基本的なケースを説明し、次にデータベースに接続するときにThreadLocalが使用される理由を分析しました。以下では、ソースコードの観点からThreadLocalの動作原理を分析します。

ThreadLocalソースコード分析

ThreadLocalクラスインターフェイスの概要

ThreadLocalクラスのインターフェースは非常に単純で、メソッドは4つしかないので、最初に理解しましょう。

  1. void set(Object value); //現在のスレッドのスレッドローカル変数値を設定します
  2. public Object get(); //このメソッドは、現在のスレッドに対応するスレッドローカル変数を返します
  3. public void remove(); //スレッドローカル変数の値が削除された場合、その目的はメモリ使用量を減らすことです。このメソッドはJDK5.0の新しいメソッドです。現在のスレッドが終了すると、対応するスレッドのローカル変数が自動的にガベージコレクションされるため、このメソッドを呼び出してスレッドのローカル変数をクリアする必要はありません。動作しますが、メモリリカバリの速度を上げることができます。
  4. protected Object initialValue() //スレッドのローカル変数の初期値を返します。このメソッドは保護されたメソッドであり、明らかにサブクラスがカバーできるように設計されています。このメソッドは遅延呼び出しメソッドであり、スレッドがget()またはset(Object)を初めて呼び出すときに、1回だけ実行されます。initialValueを書き込まない場合、get()を最初に呼び出すとnullが返されます。
  5. public final static ThreadLocal <String> resourse = new ThreadLocal <String>();  resourseは、文字列型を格納できるThreadLocalオブジェクトのみを表します。現時点では、どのスレッドがこの変数に同時にアクセスしても、その変数に対する書き込み操作と読み取り操作はすべてスレッドセーフです。

 

ソースコードから段階的に古典的なフローチャートを描く

実際の開発におけるThreadLocalの使用プロセスに従って、インターネット上のいたるところに広がる古典的なフローチャートを段階的に描きます。(注意深く見てください、インタビュアーのスリングを100%理解しています!)

 

実際、この絵を描くのに必要なコードは3行だけです。注:ThreadLocalは、例を作成するためのローカルメソッドとしてのみ設定されます。ThreadLocalがローカル変数として設定されている場合、スレッド分離の独自の機能が失われます。それはまさにアリを攻撃する核爆弾の操作です!

 

 

まず、手順1と同様に、新しいThreadLocalオブジェクトを作成します。

 

ThreadLocalが設定されていない場合、データがないことがわかっているので、手順2に進んで値の設定開始します。セットをクリックしてソースコードをご覧ください!

 

ThreadLocalのsetメソッドをクリックすると、最初のステップで現在のスレッドのオブジェクトを取得していることがわかります。現在のスレッドのオブジェクトのライフサイクルは現在のスレッドと同期していることに注意してください。したがって、フローチャートを更新します。

 

次に、現在のスレッドオブジェクトに基づいてThreadLocalMapを取得します(このThreadLocalMapは常に存在するわけではありませんが、現在このThreadLocalMapオブジェクトがあるかどうかを確認するために、存在しない場合は最初にオブジェクトを作成します。存在しない場合は、ThreadLocalMapを直接取得します。オブジェクト)。したがって、更新プロセスは次のとおりです。

 

マップオブジェクトを取得した後、現在のスレッドのThreadLocalMapオブジェクトの設定を開始します。

 

ここでのセットの鍵はこれであることに注意してください。この時点でのこのオブジェクトは、次の図に示すように、ThreadLocalオブジェクトです。

 

では、このThreadLocalMapオブジェクトのsetメソッドは何をするのでしょうか。私たちは入り続けて見ます。

 

私たちはそれを見ることができます。データを再処理して、エントリ配列に配置しました。では、このエントリ配列は何ですか?最初にプロセスを更新します。

 

Entryクラスの構造を見てみましょう。

 

Entryの構造は地図と非常によく似ており、最も重要なことはここにあります。このエントリの鍵は、ここでの弱参照です。何?弱い参照?これは何のため?心配しないでください、あなたの疑いを保ちなさい。上記の手順に従ってフローチャートを更新しましょう。

 

 

最後に、この時点で、それは私たちの最も古典的な絵と一致します。この時、息を呑み、いよいよ完成です!しない!話が止まらない。最も重要なステップがあります。

弱参照の特徴は、GCの後、オブジェクトとの接続が切断されることです。次に、プログラムが一定期間実行され、ランダムGCの後、メモリグラフ全体が次のようになります。これは、メモリ内のデータの最終的な配布です。

 

それから誰かがもう一度言いましたか?いいやつ、あなたの写真はこんな感じです、ref.get()メソッドで値を取得できますか?シャオアン、心配しないでください、これはあなたが見続けるのにあなたを連れて行きます。

 

現在のスレッドのThreadLocalデータを取得すると、現在のスレッドも取得し、それをThreadLocalMapに委任して再度クエリを実行することがわかりました。その後のプロセスは次のようになります。

 

ステップ1の存在目的から、現在のスレッドのステップ2に入り、キーがrefである現在のスレッドの値データを取得します。モーゼルが突然始まったような気がしますか!ついにそれを1日と呼べますか?あなたが息を吸う準備ができているとき、私はまだ言いません!ブロガーは最初から疑いを持っていたからです。つまり、refオブジェクトの参照がエントリのキーによって切断された場合、エントリのキーはnullになりませんか?私たちは答えを明らかにし続けます。

 

弱い引用の解釈

Javaには、強、弱、弱の4種類の参照があることがわかっています。弱参照の定義は、gcが発生している限り、参照チェーンが切断されることです。プログラムで弱参照をテストしてみましょう。

まず、自由にテストクラスを定義しましょう。

 

次に、このクラスを参照するために弱参照を使用します。次のプログラムでGCが発生した後、wrTestの結果がnullであるかどうかをテストします。

 

 

この時点で、オブジェクトが実際にnullであることがわかります。このとき、文言を変更します。

 

 

え?ここに問題があります。この弱い参照の値がGC後も利用できるのはなぜですか?弱参照の参照チェーンが消えていませんか?いいえ、真実は、この時点での新しいTest()オブジェクトもたまたま強力なテスト参照によって示されているため、GCが発生してもリサイクルできないということです。これは、エントリキーがThreadLocalの新しいThreadLocal()の参照チェーンから切断されるシナリオと完全に一致していますが、それでもnullではありません。

 

 

弱参照が指すオブジェクトが弱参照から切断された場合でも、オブジェクトが他の場所で参照されていてリサイクルできない場合、弱参照は切断前に接続アドレスを介して値を取得できるという結論に達しました。 。つまり、参照の切断は、参照のアドレス指定機能に影響を与えません。参照の切断は、参照チェーンの切断のみを引き起こし、オブジェクトはGCによってリサイクルされますが、!この時点で強い参照、次に弱い参照オブジェクトの参照チェーンがなくても引き続きアクセスできます(ここでは少し拡張されています。オブジェクトのアドレスを強制的に変更すると、弱い参照はアクセスできなくなります。追跡を続ける)。

簡単なケースを考えてみましょう。電車に乗るためのチケットを購入し、座席を見つけて入場するとします。でも、記憶力が悪いので、トイレに行って帰ってくると席が見つかりません。このとき、車掌はチケット購入ファイルに従っていつでも座席番号を見つけることができます。

これまでのところ、ThreadLocalのソースコード図は終わりに達する可能性があります。

ThreadLocalMapのキーを弱参照として設定する必要があるのはなぜですか?

ThreadLocalがリサイクルされるシーン

 

まず第一に、この仮定の前提は、ThreadLocalの使用法が適切に行われていないことであり、これはエレガントではないことを強調ます。なぜブロガーはそう言うのですか?ThreadLocalは、各スレッドで直接独立してコピーを作成する機能を備えているため、通常はpublic staticfinalで装飾します。つまり、この参照が偶然に消えることはありません。

あなたの参照はpublicstatic finalで装飾されて消えることはありませんが、スレッドは終了すると主張する人もいますか?上記のプロセスを注意深く読めば、現在のスレッドのThreadLocalMapに基づいてThreadLocal値が取得されることを読者は非常に明確に理解しているはずです。現在のスレッドが終了すると、スレッドのThreadLocalMapオブジェクトは一緒に消えます。対応するエントリも一緒に消えます(後で説明します)

メモリリークの原因

 

前にプロセスを説明したときに、ThreadMapのエントリは弱参照であると述べました。

 

したがって、現時点では、ThreadLocalMapのエントリキーは強力な参照であると考えています。参照がスタックからポップアウトされると、1行目が切断された後、Entryには常に新しいThreadLocalを指す参照番号2があります。 ()オブジェクト。このオブジェクトにアクセスしたり、リサイクルしたりすることはできず、メモリリークが発生します。

 

この恥ずかしさを避けるために、エントリキーと新しいThreadLocalオブジェクトは弱参照として設定されます。(私たちの2人の兄弟は一度連絡を取ることができ、将来あなたが借金を回収するのを見つけても大丈夫です。あなたが死んでいるか生きているかを制御することはできません)。本当にオブジェクトをツールパーソンと見なしてください!

弱参照として設定した後のGCのメモリモデルは次のとおりです。

 

現時点では、refがスタックからポップされると、新しいThreadLoalが分離されて無力になり、リサイクルされるだけです。この時点で、最も一般的なメモリリークについて説明します。

多くのオンラインブログはこの方法で分析されます。光の理論の観点からは理にかなっていますが、実際には、ThreadLocalを深く理解していません。

 

ステップ1が切断されると、ステップ2はガベージコレクションによって再び切断され、オブジェクトは分離して復元されます。ここで私は自信を持って言います:1が切断される前に、2つは実際に対象と完全に別れ、もはやそれとは何の関係もありませんでした!それでもわからない場合は、上記の分析プロセスを引き続き確認してください。

エントリキーのメモリリーク

以前に読んだブログでは、ThreadLocalオブジェクトのメモリリークについて最も多く言及されていました。しかし、実際には、エントリに実際にリークがあることがわかりました。図に示すように、ThreadLocalオブジェクトのリサイクルが成功したため、キーは「最終的に」nullになります。ただし、値はまだ存在しているため、キーがnullであるため、このデータグループの値にアクセスできず、メモリリークが発生します。

うん!これは可能です、私が以前読んだブログで誰もそれについて言及したことがありません!心配しないでください。ThreadLocalがどのように応答するか見てみましょう。

 

セットの最適化

 

この時点で、エントリの添え字iに対応するキー値がnullの場合、それはキーがリサイクルされたことを意味します。したがって、とにかく、キーはnullであり、使用できません。

最適化を取得

 

 

 

 

ご覧のとおり、getがキーがnullであることを検出する方法は、エントリから直接キーを強制的に削除することです。

メソッドを削除します

 

削除は、エントリをクリーンアップするためにアクティブにトリガーする方法です。getメソッドの下部で同じメソッドが呼び出されます。リークしたメモリの回復を加速できます。したがって、スタック内の参照がnullになった場合は、remove()メソッドを再度呼び出して、ThreadLocalMapのエントリをクリーンアップできます。(より時間に敏感)

 

スレッドが終了するときに最適化する

最後に、スレッドが終了すると、Threadクラスがクリーンアップされます。これには、ThreadLocalMapのクリーンアップが含まれます。

スレッドはexit()実行メソッドを終了します。

 

 

 

ThradLocalはローカル変数として設定できます。それは可能ですが意味がなく、メモリリークのリスクがあります。

メモリリークについてはこれだけです!実際、メモリリークの原因は、このThreadLocalがローカル変数に設定されていることであることがわかりました。これにより、スレッドが終了する前にThreadLocalオブジェクトがリサイクルされます。このとき、スレッドが終了するまで解放できないメモリリークのリスクがあります。このように記述する必要がある場合は、ThreadLocalオブジェクトをリサイクルしてメモリを時間内に解放するときに、remove()メソッドを呼び出すことを忘れないでください。

さらに、threadLocalがローカル変数に設定されている場合、同じスレッド内の他のメソッドはオブジェクトを取得できません。これは、同じスレッドで同じ変数を共有するというThreadLocalの本来の意図からも逸脱しています。核爆弾はアリを殺します。

ThreadLocalを誤って使用すると、スレッドが安全でなくなります

 

 

この図から、ThreadLocalが同じオブジェクトを操作する場合、すべての操作が同じインスタンスを指していることがわかります。上記のプログラムを正常に実行する場合は、ThreadLocalごとに新しいインスタンスを保持する必要があります。

おすすめ

転載: blog.csdn.net/weixin_47184173/article/details/111992933
おすすめ