通常のビジネス開発では、ThreadLocalには2つの典型的な使用シナリオがあります
シナリオ1:
ThreadLocalは、各スレッドの排他オブジェクトを保存して各スレッドのコピーを作成するために使用されます。これにより、各スレッドは他のスレッドのコピーに影響を与えずに独自のコピーを変更でき、スレッドの安全性を確保できます。
シーン2:
ThreadLocalは、他のメソッドが情報をより簡単に取得できるように、情報を各スレッドに個別に保存する必要があるシナリオとして使用されます。以前に実行されたメソッドが情報を保存した後、グローバル変数の概念と同様に、パラメーターの受け渡しを回避するThreadLocalを介して後続のメソッドを直接取得できます。
典型的なシナリオ1
このシナリオは通常、スレッドに対して安全でないツールクラスを保存するために使用されます。使用する必要がある一般的なクラスはSimpleDateFormatです。
この場合、各スレッドにはインスタンスの独自のコピーがあり、そのコピーは現在のスレッドでのみアクセスおよび使用できます。これは、各スレッド内のローカル変数に相当します。これは、ThreadLocal命名の意味でもあります。各スレッドには共通のコピーではなく独自のコピーがあるため、複数のスレッド間で共有する問題はありません。
たとえば、SimpleDateFormatを使用する必要がある10のスレッドがあります。
public class ThreadLocalDemo01 { public static void main(String [] args)throws InterruptedException { for(int i = 0; i <10; i ++ ){ int finalI = i; new Thread(()-> { 文字列データ = new ThreadLocalDemo01()。date(finalI); System.out.println(data); })。start(); Thread.sleep( 100 ); } } private String date(int seconds){ 日付日付 = 新しい日付(1000 * 秒); SimpleDateFormat simpleDateFormat = new SimpleDateFormat( "mm:ss" ); simpleDateFormat.format(date);を返します。 } }
スレッドごとにSimpleDateFormatオブジェクトを作成しました。これらは互いに影響しません。コードは通常どおり実行できます。出力結果:
00:00 00:01 00:02 00:03 00:04 00:05 00:06 00:07 00:08 00:09
この現在の状態をグラフで見てみましょう。
SimpleDateFormatオブジェクトが1,000スレッドで使用されている場合はどうなりますか?
通常、直接作成するスレッドはそれほど多くありませんが、次のようなスレッドプールを介して作成されます。
パブリック クラスThreadLocalDemo011 { public static ExecutorService threadPool = Executors.newFixedThreadPool(16 ); public static void main(String [] args)throws InterruptedException { for(int i = 0; i <1000; i ++ ){ int finalI = i; threadPool.submit(() -> { String data = new ThreadLocalDemo011()。date(finalI); System.out.println(data); }); } threadPool.shutdown(); } プライベートString date(int seconds){ Date date = new Date(1000 * seconds); SimpleDateFormat simpleDateFormat = new SimpleDateFormat( "mm:ss" ); simpleDateFormat.format(date);を返します。 } }
16スレッドのプールを使用し、1,000のタスクをこのスレッドプールに送信したことがわかります。各タスクで以前と同じことを行うか、dateメソッドを実行して、このメソッドで作成します
simpleDateFormatオブジェクト。結果:
00:00 00:07 00:04 00:02 ... 16:29 16:28 16:27 16:26 16:39
今行ったのは、タスクごとにsimpleDateFormatオブジェクトを作成することです。つまり、1000のタスクは1000のsimpleDateFormatオブジェクトに対応しますが、タスクの数が膨大な場合はどうでしょうか。
非常に多くのオブジェクトの作成にはコストがかかり、使用後の破棄にもコストがかかり、メモリ内のメモリの浪費でもあります。
すべてのスレッドがsimpleDateFormatオブジェクトを共有する必要があると思いますか?ただし、simpleDateFormatはスレッドセーフではないため、synchronizedを使用してロックするなどの同期を行う必要があります。ここに私たちの最終的な解決策があります。ただし、同期ロックを使用するとキュー状態になり、複数のスレッドが同時に動作できないため、全体的な効率が大幅に低下します。より良い解決策はありますか?
ThreadLocalを使用する
この種のシナリオでは、ThreadLocalがより適切です。ThreadLocalは、各スレッドのsimpleDateFormatオブジェクトを維持します。このオブジェクトは、スレッド間で独立しており、互いに何の関係もありません。これにより、スレッドセーフの問題も回避されます。同時に、simpleDateFormatオブジェクトはあまり作成せず、スレッドプールには16のスレッドしかないため、16のオブジェクトが必要です。
パブリック クラスThreadLocalDemo04 { public static ExecutorService threadPool = Executors.newFixedThreadPool(16 ); public static void main(String [] args)throws InterruptedException { for(int i = 0; i <1000; i ++ ){ int finalI = i; threadPool.submit(() -> { String data = new ThreadLocalDemo04()。date(finalI); System.out.println(data); }); } threadPool.shutdown(); } プライベート String date(int seconds){ Date date = new Date(1000 * seconds); SimpleDateFormat dateFormat = ThreadSafeFormater.dateFormatThreadLocal.get(); return dateFormat.format(date); } } クラスThreadSafeFormater { public static ThreadLocal <SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(()-> new SimpleDateFormat( "mm:ss" )); }
この現在の状態をグラフで見てみましょう。
典型的なシナリオ2
各スレッドは、グローバル変数と同様の情報(インターセプターで取得したユーザー情報など)を保存する必要があります。これは、パラメーターの受け渡しの問題を回避するためにさまざまなメソッドで直接使用できますが、複数のスレッドで共有したくない(ユーザーがさまざまなスレッドで取得したため)情報は異なります)。
たとえば、ThreadLocalを使用して一部のビジネスコンテンツ(ユーザー権限情報、ユーザーシステムから取得したユーザー名、ユーザーIDなど)を保存します。情報は同じスレッドで同じですが、異なるスレッドは異なるビジネスコンテンツを使用します。
スレッドライフサイクルでは、静的なThreadLocalインスタンスのget()メソッドによって設定されたすべてのオブジェクトを取得して、このオブジェクト(ユーザーオブジェクトなど)をパラメーターとして渡す問題を回避します。
たとえば、ユーザーシステムの場合、リクエストが届くと、スレッドがリクエストの実行を担当し、リクエストはservice-1()、service-2()、service-3()、serviceを順に呼び出します。 -4()、これらの4つのメソッドは異なるクラスに分散される場合があります。
写真の形式で例を示します。
コード:
パッケージcom.kong.threadlocal; パブリック クラスThreadLocalDemo05 { public static void main(String [] args){ User user = new User( "jack" ); new Service1()。service1(user); } } class Service1 { public void service1(ユーザーuser){ // ThreadLocalに値を割り当てます。後続のサービスは、ThreadLocalから直接取得できます。 UserContextHolder.holder.set(user); new Service2()。Service2(); } } クラスService2 { public voidservice2(){ User user = UserContextHolder.holder.get(); System.out.println( "User that service2 got:" + user.name); new Service3()。service3(); } } class Service3 { public void service3(){ User user = UserContextHolder.holder.get(); System.out.println( "User that service3 got:" + user.name); // プロセス全体が実行された後、remove UserContextHolderを 実行する必要があります.holder.remove(); } } class UserContextHolder { // ThreadLocalを作成してUserオブジェクトを保存する public static ThreadLocal <User> holder = new ThreadLocal <> (); } class User { 文字列名; public User(String name){ this .name = name; } }
実装の結果:
Service2ユーザー:ジャック
Service3ユーザー:ジャック