JDK 1.8同時進行のThreadLocalソース解析

9184 ワード

本文の目的はThreadLocalのソースコードを分析することです。ThreadLocalの使い方について、参考資料を読んでください。
ThreadLocal.ThreadLocal Map
各スレッドオブジェクトには、ThreadLocal Mapタイプの変数があります。ThreadLocal MapはThreadLocalの内部クラスであり、線形探知法に基づく分散リストで実現される。各スレッドオブジェクトは、Mapに複数のThreadLocalオブジェクトをキーとするキーペアを追加することができ、各キーに対応する値は一意です。したがって、一つのThreadLocalオブジェクトによって設定された値は、スレッドごとに一意であり、互いに独立している。唯一の理由はキーの唯一性であり、個々のスレッドには独自のThreadLocarMap内部変数があり、それはすべてスレッドに帰属しているからである。
// java.lang.ThreadLocal.ThreadLocalMap

ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
興味深いことに、その元素は弱い引用であり、弱い引用によって関連された対象は次のゴミ収集が発生する前に生存するしかない。ゴミ収集器が作動すると、現在のメモリが十分かどうかにかかわらず、参照対象のみを回収します。このため、あるスレッドがすでに実行済みであれば、ここに引用があっても回収できないことはなく、このキーは回収後にnullになります。
したがって、ThreadLocalMapset方法では、rehash ,rehashを呼び出すことができ、これらはすでにnullになっている弱い参照を整理し、後の部分Entryに対してハッシュハッシュハッシュハッシュハッシュハッシュハッシュハッシュを再実行し、最後に拡張するかどうかを判断する。おすすめの参考資料3)
static class Entry extends WeakReference> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal> k, Object v) {
        super(k);
        value = v;
    }
}
ThreadLocalコンストラクタ
public ThreadLocal() {
}
コンストラクタは空です
ThreadLocalのset方法
現在のスレッドのThreadLocal Mapオブジェクトにキーペアを追加します。キーはThreadLocalオブジェクトで、設定されたvalueオブジェクトの値です。
public void set(T value) {
    //       
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    //      set get   ,    map      ,    ThreadLocal  
    if (map != null)
        map.set(this, value);
    //      set get   ,         ThreadLocalMap
    //        ,    ThreadLocal  
    else
        createMap(t, value);
}

//   Thread  t ThreadLocalMap  ,   null
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalのget方法
スレッドでgetメソッドを呼び出した場合、以前にset方式でこのThreadLocalキーに値を追加した場合、設定した値を返します。そうでなければデフォルトの初期値nullに戻ります。
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    //      set get   ,        ,        
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //      set get   ,        ,        ,      
    return setInitialValue();
}

private T setInitialValue() {
    //      
    T value = initialValue();
    // set  
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
ThreadLocalのinitial Value
通常、ThreadLocalオブジェクトを新規作成する場合、共有の初期値を設定するために、匿名の内部クラスでこの方法を書き換えることができる。詳細は参考資料1による。
//           ,    ThreadLocal            
protected T initialValue() {
    return null;
}
Inheitable ThreadLocalInheritableThreadLocalを使用して、親スレッドがnewスレッドの前に保持されているInheritableThreadLocal変数のオブジェクト値をサブスレッドで取得することができる。
public class Main {
    private static InheritableThreadLocal local =
            new InheritableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        local.set(1001);
        new Thread(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +
                    ":" + local.get());
        }).start();
        Thread.sleep(1000);
        local.set(2002);
        System.out.println(Thread.currentThread().getName() +
                ":" + local.get());
    }

}

/*    :
    main:2002
    Thread-0:1001
*/
これは、スレッドごとに作成時に、親スレッドオブジェクトのinheitable ThreadLocars属性がコピーされるからです。親スレッドのinherityable ThreadLocarsオブジェクトが空でない場合は、子スレッドにコピーし、対象参照(クローンではない)をコピーした後、親スレッドがinherityable ThreadLocal変数setに新しい値は子スレッドに影響しませんが、親スレッド修正値オブジェクトが山にあるフィールドであれば、子スレッドの値も変化します。
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}

// inheritThreadLocals = true
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    ...
    //      
    Thread parent = currentThread();
    ...
    //      inheritableThreadLocals       ,          。
    //         ,       inheritableThreadLocal    set   
    //         ,                 ,        
    //   
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    ...
}
ThreadLocal使用シーン
  • のいくつかのデータはスレッドを作用領域とし、異なるスレッドは異なるデータのコピーを有する。Androidメッセージシステムでは、スレッドごとに独自のLooperが必要であり、ThreadLocalを通じてLooperのスレッドへのアクセスが簡単に行えます。
  • 複雑な論理でのオブジェクト転送。例えば、タスクが複雑な場合は、関数コールスタックがより深いですが、また、モニターがスレッド全体を通してプロセスを実行する必要があります。ThreadLocalを使って、モニターをスレッド内のグローバルオブジェクトにすることができます。
  • 参考資料
  • ThreadLocalの設計理念と役割
  • JavaにおけるThreadLocalとInheitable ThreadLocal
  • ThreadLocalの原理解析(2):ThreadLocal Mapソース解析
  • Javaにおける強、軟、弱、虚引用