ThreadLocal源碼解析

今天在看Spring 3.x企業應用開發實戰,第九章 Spring的事務管理,9.2.2節ThreadLocal的接口方法時,書上有提到Threadlocal的簡單實現,我就去看了下JDK1.8的Threadlocal的源碼。發現實現方式與書中講的並不相同,同時在網上搜索了一下,發現有比較多的人理解錯了。

先看一下容易誤導的解釋:在ThreadLocal類中有一個Map對象,這個Map以每個Thread對象爲鍵,保存了這個線程對應局部變量值,對應的實現方式如下:

public class SimpleThreadLocal {
    private Map valueMap = Collections.synchronizedMap(new HashMap());

    public void set(Object newValue) {
        valueMap.put(Thread.currentThread(), newValue);//①鍵爲線程對象,值爲本線程的變量副本
    }

    public Object get() {
        Thread currentThread = Thread.currentThread();
        Object o = valueMap.get(currentThread);//②返回本線程對應的變量
        if (o == null && !valueMap.containsKey(currentThread)) {//③如果在Map中不存在,放到Map 中保存起來。
            o = initialValue();
            valueMap.put(currentThread, o);
        }
        return o;
    }

    public void remove() {
        valueMap.remove(Thread.currentThread());
    }

    public Object initialValue() {
        return null;
    }
}

爲什麼不按照那種有誤的方法實現呢?
看起來似乎是不同線程獲取了各自的值,但是這些值並沒有線程獨立。線程A可以操作線程B對應的值。如果某個線程將保存這些值的Map置爲null了,那麼其他線程也無法訪問了。

實際上是怎樣的呢
我們看ThreadLocal的get()方法源碼。可以看到獲取Threadlocal中的值,是先通過當前線程的線程對象t,獲取t的ThreadlocalMap屬性對象,然後再以Threadlocal對象爲鍵,去獲取ThreadlocalMap中的值。

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//獲取線程對象的屬性
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//這裏的this是指Threadlocal對象,Threadlocal在類中通常以靜態屬性出現,所以多個線程的Threadlocal指向同一個對象。
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

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

同時我們查看ThreadLocal源碼中定義的靜態類ThreadLocalMap,其實底層封裝的是一個Entry數組,獲取方式和普通的HashMap不太一樣,如果沒有命中,就直接通過線性搜索,因爲ThreadLocalMap需要保存的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);                 
}

private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

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

通過ThreadLocal,每個線程保存自身的數據,不能訪問到其他線程的數據。

ThreadLocalMap的Entry使用ThreadLocal的WeakReference引用作爲Key值,當所有線程運行出ThreadLocal的作用域時,即沒有強引用ThreadLocal時,ThreadLocal就會被回收。

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

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

不是Threadlocal爲每個線程提供了獨立的變量,而是每個線程自己帶了自己獨立的變量。

關於內存泄漏
關於ThreadLocalMap的內存泄漏:如果一個ThreadLocal的生命週期結束,即在ThreadLocal所處的類中沒有了強引用,而Thread沒有結束,在Thread的threadLocals成員變量中,會有一個Entry使用弱引用引用了ThreadLocal作爲key,因爲是弱引用,這個key將被回收。而value是強引用,看起來是會造成泄漏,但是在ThreadLocalMap的set和get方法中,有一些釋放的方法。具體的我也不太懂。
還是老老實實使用ThreadLocal的remove方法比較好。

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}
發佈了44 篇原創文章 · 獲贊 37 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章