[java]ThreadLocal原理とメモリリークの解決


1.ThreadLocalはどんな問題を解決しましたか?
ThreadLocalはスレッドローカル変数を提供し、通常はいくつかのリソースまたは変数を維持し、スレッド競合または同期問題を避ける。
2.使い方
各スレッドは{@code get}または{code set}方法により、独立して初期化された変数のコピーを作成します。
3.原理は何ですか?
3.1データを読み出す
3.1.1データを読み出す全体の流れ
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
3.1.2現在のスレッドからThreadLocal Map参照を取得する
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
3.1.2現在のスレッドにThreadLocal Map参照がない場合、ThreadLocal Mapオブジェクトを作成し、initial Valueを呼び出して初期値を取得する。その中でkeyはThreadLocalの対象であり、valueは初期値である。
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
3.1.3 ThreadLocal Mapオブジェクトの新規作成
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
3.2設定値
設定値とは、ThreadLocal Mapに値を詰め込むことであり、keyはThreadLocalオブジェクトであり、valueは設定が必要な値である。
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
4、存在するかもしれないメモリ漏れ問題?
4.1 ThreadLocarMapの構造関数では、導入されたkey、valueをEntryに組み上げて対応する線形表に入れる。
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);
}
4.2しかしEntryのkeyは弱い引用WeakReferenceを使用しています。
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
弱い引用も必要でないオブジェクトを記述するために使用されますが、その強度はソフト参照よりも弱く、弱い引用に関連するオブジェクトは次のゴミ収集が発生する前に生存することができます。ゴミ収集器が作動すると、現在のメモリが十分かどうかにかかわらず、弱い引用関連の対象だけが回収されます。
もしThreadLocalがThreadLocal Map以外の対象に引用されなかったら、次回GCの時、ThreadLocalの実例は回収されます。ThreadLocal MapのThreadLocalが示すkeyはnullです。valueは外部から訪問されません。Threadの例がずっと存在する限り、ここでkeyはnullのvalueでメモリを占有しています。これはメモリリークです。
4.3どのようにメモリ漏れを予防しますか?
ThreadLocalの使用が終了すると、ThreadLocal.remove()を呼び出してvalueの引用を解放し、ThreadLocalの対象が回収された時にvalueがアクセスできずメモリを占有することを避ける。
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}