ThreadLocalの設計と使用


ThreadLocalって何?
ThreadLocalって何ですか?実はThreadLocalはスレッドのローカル実装バージョンではなく、Threadではなくthread local variable(スレッドローカル変数)です.それをThreadLocalVarと命名するのがもっと適当かもしれません.スレッドローカル変数(ThreadLocal)の実際の機能は非常に簡単で、変数を使用するスレッドごとに変数値のコピーを提供し、他のスレッドのコピーと衝突することなく、スレッドごとに独立して自分のコピーを変更することができます.スレッドの観点から見ると、各スレッドが変数を完全に持っているように見えます.スレッドローカル変数はJavaの新しい発明ではなく、IBM XL FORTRANなどの他の言語コンパイラ実装では、言語の階層で直接サポートされています.Javaでは言語階層での直接的なサポートは提供されず、ThreadLocalのクラスでサポートが提供されているため、Javaではスレッドローカル変数を記述するコードが相対的に不器用であり、これはスレッドローカル変数がJavaでよく普及していない原因の一つかもしれない.
  
ThreadLocalのデザイン
まず、ThreadLocalのインタフェースを見てみましょう.
  
Object get() ; 
//                 
protected Object initialValue(); 
//                   
void set(Object value); 
//                  

ThreadLocalには3つの方法があり、その中で注目すべきはinitialValue()であり、この方法はprotectedの方法であり、明らかにサブクラスの書き換えのためにわざわざ実現されている.このメソッドは、現在のスレッドがスレッドローカル変数の初期値を返します.このメソッドは、1つのスレッドがget()またはset(Object)を1回目に呼び出したときに実行され、1回のみ実行される遅延呼び出しメソッドです.ThreadLocalの実装はnullを直接返します.
  
  protected Object initialValue() { return null; }
ThreadLocalはどのようにして各スレッドの変数のコピーを維持しますか?実装の構想は簡単で,ThreadLocalクラスには各スレッドの変数のコピーを格納するためのMapがある.たとえば、次のような実装例があります.
  
  
public class ThreadLocal
  {
  private Map values = Collections.synchronizedMap(new HashMap());
  public Object get()
  {
  Thread curThread = Thread.currentThread();
  Object o = values.get(curThread);
  if (o == null && !values.containsKey(curThread))
  {
  o = initialValue();
  values.put(curThread, o);
  }
  return o;
  }
  public void set(Object newValue)
  {
  values.put(Thread.currentThread(), newValue);
  }
  public Object initialValue()
  {
  return null;
  }
  }

もちろん,これは工業的強度の実現ではないが,JDKにおけるThreadLocalの実現の全体的な考え方もこれに類似している.
  
ThreadLocalの使用
スレッドローカル変数が他の値を初期化する場合は、ThreadLocalのサブクラスを実装して書き換える必要があります.通常、内部匿名クラスを使用してThreadLocalをサブクラス化します.たとえば、次の例では、SerialNumクラスはクラスごとにシーケンス番号を割り当てます.
  
  
public class SerialNum
  {
  // The next serial number to be assigned
  private static int nextSerialNum = 0;
  private static ThreadLocal serialNum = new ThreadLocal()
  {
  protected synchronized Object initialValue()
  {
  return new Integer(nextSerialNum++);
  }
  };
  public static int get()
  {
  return ((Integer) (serialNum.get())).intValue();
  }
  }

SerialNumクラスの使用は、get()メソッドがstaticであるため、現在のスレッドのシーケンス番号を取得する必要がある場合に簡単に呼び出されます.
  

  int serial = SerialNum.get();

できます.
スレッドがアクティブであり、ThreadLocalオブジェクトがアクセス可能である場合、スレッドはスレッドのローカル変数のコピーへの暗黙的な参照を保持し、スレッドの実行が終了すると、スレッドが所有するため、スレッドのローカル変数のコピーは失効し、ゴミ収集器の収集を待つ.
  
ThreadLocalと他の同期メカニズムの比較
ThreadLocalは他の同期メカニズムと比べてどのようなメリットがありますか?ThreadLocalと他のすべての同期メカニズムは、マルチスレッドにおける同一変数へのアクセス競合を解決するためであり、通常の同期メカニズムでは、オブジェクトロックによって複数のスレッドによる同一変数への安全なアクセスを実現する.このとき,この変数は複数のスレッドで共有されており,この同期メカニズムを用いて,変数をいつ読み書きするか,いつオブジェクトをロックするか,いつオブジェクトのロックを解放するかなどを細かく分析する必要がある.これらはすべて、複数のスレッドがリソースを共有しているためです.ThreadLocalは別の角度からマルチスレッドの同時アクセスを解決し、ThreadLocalは各スレッドに対して1つのスレッドとバインドされた変数のコピーを維持し、複数のスレッドのデータを隔離し、各スレッドは独自の変数のコピーを持ち、その変数を同期する必要もない.ThreadLocalはスレッドセキュリティの共有オブジェクトを提供しており、マルチスレッドコードを記述する際に、安全でない変数全体をThreadLocalにカプセル化したり、そのオブジェクトのスレッド固有の状態をThreadLocalにカプセル化したりすることができる.
ThreadLocalでは任意のタイプのオブジェクトを持つことができるため、ThreadLocal getを使用する現在のスレッドの値は、強制タイプ変換が必要です.しかし、新しいJavaバージョン(1.5)がテンプレートを導入するにつれて、新しいテンプレートパラメータをサポートするThreadLocalクラスが恩恵を受けます.強制型変換を減らし、いくつかのエラーチェックをコンパイル期間に繰り上げることで、ThreadLocalの使用をある程度簡略化することもできます.
  
まとめ
もちろんThreadLocalは同期メカニズムに取って代わるものではなく,両者が向いている問題領域は異なる.同期メカニズムは、複数のスレッドが同じリソースに対する同時アクセスを同期するためであり、複数のスレッド間で通信を行うための有効な方法である.一方、ThreadLocalは、複数のスレッドを分離したデータ共有であり、複数のスレッド間でリソース(変数)を共有することは根本的に行われていないため、複数のスレッドを同期する必要はありません.したがって、複数のスレッド間で通信する必要がある場合は、同期メカニズムを使用します.複数のスレッド間の共有競合を分離する必要がある場合は、ThreadLocalを使用します.これにより、プログラムがより読みやすく、簡潔になります.