Java ThreadLocal分析

4783 ワード

参考:ThreadLocalとWeakReference
スレッド閉鎖
共有された可変データにアクセスする場合、通常は同期を使用する必要があります.同期を避ける方法の1つは、データを共有しないことです.どのようにデータが1つのスレッドにのみアクセスされるかは、同期を必要とせず、この技術はスレッド閉鎖である.
ThreadLocalはその一つの実装です.
ThreaLocalの一般的な使用
スレッドプール:JDBC接続オブジェクトはスレッドが安全であるとは限らないため、複数のスレッドが連携していない場合にグローバル変数を使用する場合、スレッドは安全ではありません.JDBCの接続をThreadLocalオブジェクトに保存することで、各スレッドに独自の接続があります.
    private static ThreadLocal connectionHolder = new
            ThreadLocal() {
                @Override
                protected Connection initialValue() {
                    return DriverManager.getConnection("");
                }
            };
    
    public static Condition getConnection() {
        return connectionHolder.get();
    }

このほかSpringなど様々なフレームワークではThreadLocal(コンテキストを保存するために使用)が多く使われている.
げんり
Thread
Threadオブジェクトに変数(フック)があり、Mapオブジェクトを保存します.�
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalのsetメソッドを呼び出すと、Mapオブジェクトが生成され、このMapオブジェクトがThreadオブジェクトに割り当てられます.
ThreadLocal
値を設ける
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value); //  ThreadLocal    key  
        else
            createMap(t, value);
    }

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

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

値をとる
    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();
    }

    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;
    }

    protected T initialValue() { //              
        return null;
    }

ThreadLocalは本質的に各スレッドにMapオブジェクトをバインドし、このMapオブジェクトkeyはThreadLocal自体であり、valueは設定する値である.これにより、各valueはスレッドによってのみ自分でアクセスでき、外部の他のスレッドはオブジェクトにアクセスせず、スレッドセキュリティを実現します.
ThreadとThreadLocalの関係
ThreadとThreadLocalの間には密接な関係があり、ThreadのMapはThreadLocalオブジェクト(keyとして)を持っている.
Thread
ThreadLocal
死ぬ
死ぬ
生き残る
生き残る
生き残る
死ぬ
死ぬ
生き残る
両者の間が死亡した場合、自然にGCに回収されます.両方が「生き残る」場合、GCはそれらを回収しません.では、ThreadとThreadLocalの間の「死」はどうなっているのでしょうか.
Thread死
Threadが死亡した場合、スレッド内のMapも存在しない.Mapが回収されるため、Mapに保存されているkey、valueも参照されるかどうかによって回収されるかどうかを決定し、メモリ漏洩を招くことはない.
ThreadLocal死亡
ThreadLocalが死亡した場合、スレッドは依然として生存し、Mapはまだ存在するが、MapのうちのThreadLocalをkeyとするEntryは失効し、EntryのkeyとvalueはGCされるべきである.
これを実現するために、ThreadLocalのEntryは次のように実現されます.
        static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal> k, Object v) {
                super(k);
                value = v;
            }
        }

このように、ThreadLocalが死亡した場合、Entryではkeyが弱い参照であるため、GCはオブジェクトを回収し、メモリ漏洩を招くことはありません.
一方、Entryにおけるkey対応valueの回収は、以下の方法で実現される.
        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) { // e.get() point
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

expungeStaleEntry   :
                    e.value = null; // value set null
                    tab[i] = null;
                    size--;

この方法でvalueをnullに設定し、次回GC時にそのオブジェクトをタイムリーに回収できるようにする.ThreadLocalが死亡した場合、メモリが漏れることはありません.
に注意
現在、多くの技術が使用されているスレッドプール技術であり、スレッドが頻繁に作成されて破棄されない場合、スレッドプールのスレッドがグローバルThreadLocalオブジェクトを取得し、大きな文字列オブジェクトを設定し、使用後にremove操作がなければ、スレッドとThreadLocalが生存しているため、その文字列オブジェクトは回収されない可能性がある.メモリの漏洩の原因となるため、使用が完了するとスレッドはThreadLocalのremoveメソッドを呼び出し、そのオブジェクトを直ちに除去し、不要なメモリ損失を防止することが望ましい.