ThreadLocalソース分析

23161 ワード

以下の解析はJDK 1.8に基づく.まず簡単なThreadLocalの使用例を見てみましょう.
public class Test {  
      
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();  
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();  
  
    public void set() {  
        longLocal.set(Thread.currentThread().getId());  
        stringLocal.set(Thread.currentThread().getName());  
    }  
  
    public long getLong() {  
        return longLocal.get();  
    }  
  
    public String getString() {  
        return stringLocal.get();  
    }  
  
    public static void main(String[] args) throws InterruptedException {  
        final Test test = new Test();  
  
        test.set();  
        System.out.println(test.getLong());  
        System.out.println(test.getString());  
  
        Thread thread1 = new Thread() {  
            public void run() {  
                test.set();  
                System.out.println(test.getLong());  
                System.out.println(test.getString());  
            };  
        };  
        thread1.start();  
        thread1.join();  
  
        System.out.println(test.getLong());  
        System.out.println(test.getString());  
    }  
}  

上記コード出力:
1  
main  
11  
Thread-0  
1  
main  

getのコードはとても簡単です.mapを維持していることがわかります.このmapのkeyはthreadlocalオブジェクトです.このmapは、現在のスレッドオブジェクトから取得する必要があります.
   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();
    }

このmapは、現在のスレッドオブジェクトに格納されます.
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
	//thread     。
	ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMapは、ThreadLocalに定義された静的内部クラスです.このMapはHashmapではなく,このクラスの著者自身が実現したものである.まずEntryクラスの実装であり,EntryクラスはWeakReferenceクラスを継承する.
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
         //             WeakReference,       k   super,   entry->key    。entry->value    
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

これは、このmapがスレッドオブジェクトに保存されているからです.処理しないと、スレッドオブジェクトが消えない限り、これらのkeyとvalueは永遠に存在します!WeakReferenceを使用すると、Entry内のkeyを外部参照することなく、このkey(ThreadLocalオブジェクト)は仮想マシンによって回収されます.しかしvalueはそうではありません.valueをクリアするにはresize操作まで待たなければなりません.entry配列をスキャンし、keyがnullであることを発見し、valueもnullに直接作成し、GCを保証する.
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

ThreadLocal実装ではロックは使用されていません.しかしAtomicIntegerを使用しています.これはThreadLocalクラスにサービスを提供するための静的メソッドであり、nextHashCodeである.クラスの初期nextHashCodeは0に設定され、呼び出しのたびに値が返され、0 x 61 c 88647が加算される.この値は、ThreadLocalオブジェクトの一意のシーケンス番号を示すために使用されます.
	private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode =
        new AtomicInteger();
    private static final int HASH_INCREMENT = 0x61c88647;
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }   

なぜThreadLocalには唯一の番号があるのですか?ThreadLocalは上のmapのkeyになるからです.mapの最下位は配列で実現されていることを知っています.ThreadLocalのシーケンス番号と上容量を使用すると、配列内のentryオブジェクトを迅速に位置決めできます.
        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);
        }

メモリリークの問題
ThreadLocalはThreadLocalMapで弱参照としてEntryのKeyによって参照されているため、ThreadLocalが外部から強引用されて参照されていない場合、ThreadLocalは次回のJVMゴミ収集時に回収されます.このときEntryではKeyが回収されてnull Keyが1つ発生する場合があり、外部読み出しThreadLocalMapの要素はnull KeyではValueを見つけることができません.したがって、現在のスレッドのライフサイクルが長く、常に存在する場合、その内部のThreadLocalMapオブジェクトも常に生存しており、これらのnull keyには強い参照チェーンの関係がずっと存在しています.Thread-->ThreadLocalMap->Entry->Valueです.この強い参照チェーンはEntryが回収されず、Valueも回収されません.しかし、EntryのKeyはすでに回収されており、メモリが漏れています.しかし、JVMチームは、このような状況を考慮し、ThreadLocalのメモリ漏洩を最小限に抑えるための措置を講じています.ThreadLocalのget()、set()、remove()メソッド呼び出し時にスレッドThreadLocalMapのすべてのEntryのKeyがnullのValueを消去し、Entry全体をnullに設定し、次回のメモリ回収に役立ちます.