ThreadLocalの原理と使用

22921 ワード

ThreadLocalはスレッド内部のローカル変数を提供するために使用され、この変数はスレッド間で互いに独立しており、ThreadLocalのsetメソッドとgetメソッドを呼び出すことで変数のアクセスを実現することができる.
基本的な使用方法:
public class ThreadLocalTest {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "initValue";
        }
    };
    public static void main(String[] args) {
        for (int i=0;i<5;i++){
            int finalI = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    threadLocal.set("thread-"+finalI);
                    System.out.println(threadLocal.get());
                    threadLocal.remove();
                    System.out.println(threadLocal.get());
                }
            }).start();
        }
        System.out.println("main-" + threadLocal.get());
    }
}

しゅつりょく
thread-0
thread-2
thread-3
main-initValue
thread-4
thread-1

以上のコードでは,ThreadLocalのinitialValue()メソッドを書き換えることでその初期値を設定し,メインスレッドではset()メソッドを呼び出して値を付与していないためget()メソッドを呼び出して得られる値がinitialValue()メソッドの戻り値である.ループ内のサブスレッドでは、ThreadLocalのset()メソッドを呼び出して各スレッドに値を付与すると、get()メソッドは、そのスレッドに設定された値であり、各スレッド間で互いに独立している.
ThreadLocalのset()メソッドは次のとおりです.
public void set(T value) {
   //        
   Thread t = Thread.currentThread();
   //            ThreadLocalMap  
   ThreadLocalMap map = getMap(t);
   //  map   ,    
   if (map != null)
      map.set(this, value);
   else
      //  map  ,   ThreadLocalMap    value  map
      createMap(t, value);
}

ThreadLocalのgetメソッドは次のとおりです.
public T get() {
     //        
     Thread t = Thread.currentThread();
     //            ThreadLocalMap  
     ThreadLocalMap map = getMap(t);
     //    map  ,    ThreadLocal   key,  map getEntry  Entry  
     if (map != null) {
         ThreadLocalMap.Entry e = map.getEntry(this);
         if (e != null) {
             //   entry     ,  entry    value 
             T result = (T)e.value;
             return result;
          }
      }
            //   map   ,  setInitialValue     
      return setInitialValue();
}

ThreadLocaldのsetInitialValue()メソッドは次のとおりです.
private T setInitialValue() {
    //       ,initialValue()     null,        
        T value = initialValue();
        //      
        Thread t = Thread.currentThread();
        //            ThreadLocalMap  
        ThreadLocalMap map = getMap(t);
        if (map != null)
        //  map   ,      
            map.set(this, value);
        else
        //  map  ,   ThreadLocalMap    value  map
            createMap(t, value);
        return value;
}

ThreadLocalの最下位レベルでは、ThreadオブジェクトのThreadLocalMapを使用してデータストレージを行い、ThreadLocalでcreateMap(t,value)を呼び出すと、ThreadLocalMapの構築方法が呼び出されます.以下のようになります.
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    //     Entry  ,     16
    table = new Entry[INITIAL_CAPACITY];
    //  value       
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //    Entry  ,key ThreadLocal  ,  value,       
    table[i] = new Entry(firstKey, firstValue);
    //          
    size = 1;
    //      , table   * 2/3;
    setThreshold(INITIAL_CAPACITY);
}

ThreadLocalのsetメソッドは、最終的にThreadLocalMapのset(ThreadLocal>key,Object value)メソッドを呼び出します.
private void set(ThreadLocal<?> key, Object value) {
     Entry[] tab = table;
     int len = tab.length;
     int i = key.threadLocalHashCode & (len-1);
	 for (Entry e = tab[i];
        e != null;
        e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        if (k == key) {
             e.value = value;
             return;
        }
        if (k == null) {
             replaceStaleEntry(key, value, i);
             return;
         }
     }
     tab[i] = new Entry(key, value);
     int sz = ++size;
     if (!cleanSomeSlots(i, sz) && sz >= threshold)
         rehash();
}


上記のコードでは、まずキー(ThreadLocalオブジェクト)の値に基づいてその要素の格納位置を計算し、table配列を巡り、entryが空でない場合、キーを配列要素entryのthreadLocalオブジェクトと比較し、キー値がthreadLocalに対応する場合、見つかったentryオブジェクトのvalueに新しい値を割り当てます.entryのkeyが空の場合、replaceStaleEntryメソッドを呼び出してkeyが空のentryを現在設定するentryに置き換えます.配列を巡るときにentryが空の場合、新しいentryオブジェクトを作成してentryが空の位置に配置し、配列要素の個数に1を加え、最後にcleanSomeSlotsを呼び出して期限切れ要素を消去し、配列要素の個数がしきい値に達したかどうかを判断し、しきい値に達した場合、拡張を行います.
ThreadLocalのgetメソッドは、最終的にThreadLocalMapのgetEntry(ThreadLocal>key)メソッドを呼び出します.このメソッドは次のようになります.
private Entry getEntry(ThreadLocal<?> key) {
      int i = key.threadLocalHashCode & (table.length - 1);
      Entry e = table[i];
      if (e != null && e.get() == key)
          return e;
      else
          return getEntryAfterMiss(key, i, e);
}

上記のコードでは、まずkeyに対応するentryオブジェクトの格納位置を計算し、次に位置に基づいて配列からentryオブジェクトを取得し、entryオブジェクトがnullではなく、entryのkeyがメソッドに入力されたkeyと同じであればentryオブジェクトを返し、そうでなければgetEntryAfterMiss(key,i,e)メソッドを実行し、このメソッドでは配列中の要素を遍歴し、entryオブジェクトのkeyがnullでない場合、受信したkeyと比較し、同じようにentryオブジェクトを返します.entryオブジェクトのkeyがnullの場合、オブジェクトが期限切れであることを示す場合、expungeStaleEntryメソッドを呼び出して期限切れオブジェクトを消去します.