ThreadLocal,看我就夠了!

ThreadLocal


開胃菜

?研究過Handler的應(yīng)該對ThreadLocal比較眼熟的,線程中的Handler對象就是通過ThreadLocal來存放的。初識ThreadLocal的可能被它的名字有所誤導(dǎo),ThreadLocal初一看可能會覺得這是某種線程實現(xiàn),而實際并非如此。事實上,它是一個全局變量,用來存儲對應(yīng)Thread的本地變量,這也是為什么將其稱之為Local。當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本。
?比如Handler,當我們在一個線程中創(chuàng)建了一個Handler時,在調(diào)用Looper.prepare()時通過ThreadLocal保存了當前線程下的Looper對象,而所有線程的Looper都由一個ThreadLocal來維護,也就是在所有線程中創(chuàng)建的Looper都存放在了一個ThreadLocal中,然后創(chuàng)建Handler將Handler與當前線程Looper關(guān)聯(lián),當調(diào)用Looper.loop()的時候通過myLooper()得到的就是當前線程的Looper,當在其他線程使用Handler來發(fā)送消息的時候,其實也就是將對應(yīng)的Message存儲到了對應(yīng)Handler的MessageQueue中,當Looper去分發(fā)消息的時候,就是將當前線程中的Looper對應(yīng)的MessageQueue中的Message通過Handler的回調(diào)返回給了Handler所在的線程。如對Handler不了解的,可參考Handler全面解讀

threadlocal_handler.png


ThreadLocal模擬

?那么,如何來做到區(qū)分不同線程中的變量呢?我們這里模擬一個實現(xiàn)ThreadLocal功能的類,原理大致一樣。

public class ThrealLocalImitation<T> {
    private static Map<Thread, Object> sSaveValues = new HashMap<Thread, Object>();

    public synchronized void set(T threadData) {
        Thread thread = Thread.currentThread();
        mSaveValues.put(thread, threadData);
    }

    public synchronized T get() {
        Thread thread = Thread.currentThread();
        return (T) mSaveValues.get(thread);
    }
}

?如上ThreadLocal模仿類里面,通過全局的Map變量sSaveValues,以Thread為key,value為對應(yīng)線程需要保存的變量,實現(xiàn)了,每個線程對應(yīng)保存了一個變量,在不同的線程存儲不同的變量,通過get方法就能取回對應(yīng)的值。


ThreadLocal原理分析

?ThreadLocal類通過set、get方法來分別存取變量的,搞懂了這兩個方法的功能也就明白ThreadLocal的原理了,所以重點分析這兩個方法。

在進入正式的分析之前先來看一個類——ThreadLocalMap

?和我們模擬的ThreadLocal稍有所區(qū)別,ThreadLocal不是直接通過一個Map來存儲Thread和value對應(yīng)關(guān)系的。
?在Thread類中,有一個變量ThreadLocal.ThreadLocalMap。
?先看下該類的其中一個構(gòu)造方法

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

    static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

?ThreadLocalMap內(nèi)部維護一個數(shù)組Entry[] table, Entry對應(yīng)存儲了key--value(ThreadLocal---value)。ThreadLocalMap實際上是一個實現(xiàn)了自定義的尋址方式的HashMap。
?那么ThreadLocal是如何存儲線程本地變量的呢?先給個簡單的結(jié)論。
每個Thread在生命周期中都會維護著一個ThreadLocalMap,可以看成是一個存儲了ThreadLocal(key)---value的HashMap,當ThreadLocal存儲value時,先通過當前Thread得到其維護的ThreadLocalMap,然后將其存儲到該map中,而獲取value時則是先獲取到當前線程的ThreadLocalMap,然后通過當前的ThreadLocal,獲取到ThreadLocalMap存儲的value值。

set()方法

public void set(T value) {
    Thread t = Thread.currentThread();//獲取到當前線程
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

?set方法中,首先通過Thread.currentThread()獲取到當前的線程,通過當前線程獲得其維護的ThreadLocalMap,當map為空時,則為當前Thread創(chuàng)建一個ThreadLocalMap,不為空的話則將ThreadLocal--value存儲到map中。
所以一個Thread對應(yīng)著一個ThreadLocalMap,而一個ThreadLocalMap對應(yīng)著多個ThreadLocal。

get()方法

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

?get()方法,同樣是先獲取到當前Thread,然后獲取到當前Thread的ThreadLocalMap,然后根據(jù)ThreadLocal自身,通過ThreadLocalMap自身的尋址方式獲取到存儲ThreadLocal和value的Entry對象,進而得到value。
?setInitialValue();是當ThreadLocalMap為空時,可以通過實現(xiàn)ThreadLocal的initialValue()來獲得一個默認值,同時該默認值會被存儲到線程的ThreadLocalMap中。


內(nèi)存泄漏

?分析到這里,ThreadLocal的原理已經(jīng)很明朗了。但是一些使用不當?shù)那闆r出現(xiàn)內(nèi)存泄漏的風險,所以最后講解下ThreadLocal會出現(xiàn)的內(nèi)存泄漏風險,及如何避免。
?ThreadLocalMap中存儲的Entry為ThreadLocal--value,準確的描述應(yīng)該是weakReference(ThreadLocal)--value,即,key(ThreadLocal為弱引用),而value則是強引用的,當ThreadLocal為空后,Thread不會再持有ThreadLocal引用,ThreadLocal可以被GC回收,但是Thread的ThreadLocalMap仍然還持有value的強引用,導(dǎo)致value需要等待線程生命周期結(jié)束才可能被GC回收。當出現(xiàn)一些長時間存在的線程,不斷的存儲了內(nèi)存比較大的value,而value實際是不再被使用的,value由于線程沒有被回收而不斷的堆積,造成了內(nèi)存泄漏。比如當使用到線程池是,Thread很有可能不會被馬上結(jié)束,可能會被不斷的重復(fù)利用。
?所以這里引入ThreadLocal的另外一個方法——remove方法

remove()方法

    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }


    //ThreadLocalMap中的remove
    private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

?所以在value不再使用時,應(yīng)該及時調(diào)用remove,解除線程對該value的引用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容