不就是ThreadLocal

最近在重構(gòu)代碼時(shí),發(fā)現(xiàn)有不同類之間參數(shù)的傳遞很復(fù)雜,想到了之前看到的ThreadLocal,于是就想使用ThreadLocal來(lái)解決參數(shù)傳遞的問(wèn)題,但是在使用之前還是先看了下ThreadLocal的源碼,避免后面出現(xiàn)問(wèn)題。

先簡(jiǎn)單說(shuō)下ThreadLocal的實(shí)現(xiàn)原理,然后再跟著源碼看下。

每個(gè)ThreadLocal實(shí)例對(duì)應(yīng)一個(gè)當(dāng)前運(yùn)行的Thread線程,每個(gè)Thread線程又有一個(gè)ThreadLocalMap,通過(guò)ThreadLocal類的set方法將需要使用的參數(shù)保存在ThreadLocalMap這個(gè)map中,后面只要在同一個(gè)線程中利用ThreadLocal實(shí)例的get方法就可以得到之前設(shè)置的參數(shù)。
其實(shí),在看源碼之前,可以簡(jiǎn)單思考下實(shí)現(xiàn)的套路無(wú)非就是:
1. 獲取當(dāng)前線程。
2. 獲取此線程的ThreadLocalMap
3. 根據(jù)這個(gè)map的key獲取value。

那么,問(wèn)題來(lái)了,這個(gè)key是啥呢,那就開(kāi)始看下源碼吧。
首先看下ThreaLocal的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);
    }

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

其實(shí),從源碼來(lái)看,實(shí)現(xiàn)的套路和我們之前猜想的是一樣的,主要是看下這個(gè)map的key是啥,從map.set(this, value)這句可以看到,這個(gè)map的key就是ThreadLocal實(shí)例的引用。

然后,看下ThreaLocal的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方法的過(guò)程也很簡(jiǎn)單,根據(jù)threadLocal實(shí)例的引用去獲取ThreadLocalMap中Entry對(duì)象,然后獲取value值。

上面的源碼很簡(jiǎn)單,主要的問(wèn)題是ThreadLocal可能存在內(nèi)存泄露,這也是使用之前想看下源碼的原因,下面就看下為啥會(huì)出現(xiàn)內(nèi)存泄露。
內(nèi)存泄露一定是存儲(chǔ)的數(shù)據(jù)沒(méi)有及時(shí)釋放,導(dǎo)致數(shù)據(jù)占用的內(nèi)存越來(lái)越大,從上面的源碼來(lái)看,數(shù)據(jù)是存儲(chǔ)在哪里的呢,很明顯是在ThreadLocalMap中,那放在map中的數(shù)據(jù)為啥占用的內(nèi)存越來(lái)越大呢,那就要看下這個(gè)map的源碼有啥特點(diǎn)了。

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

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

從上面的源碼可以發(fā)現(xiàn),Entry對(duì)象繼承了WeakReference類,這個(gè)類的作用主要就是使某個(gè)對(duì)象的引用為弱引用,那么弱引用有啥特點(diǎn)呢,簡(jiǎn)單來(lái)說(shuō),只要觸發(fā)GC,不管這個(gè)實(shí)例有沒(méi)有被其他對(duì)象引用,都會(huì)被回收。
寫(xiě)到這里,又想到前段時(shí)間利用WeakHashMap來(lái)簡(jiǎn)單實(shí)現(xiàn)緩存功能,其實(shí)也是利用了WeakHashMap弱引用的特點(diǎn),避免緩存越來(lái)越大,導(dǎo)致內(nèi)存溢出。
繼續(xù)剛才的分析,可能不太熟悉的同學(xué)這里可能會(huì)有疑問(wèn),既然弱引用這么容易回收,那么更不可能出現(xiàn)內(nèi)存泄露了。其實(shí),只是map的key為弱引用,那么key回收后就變?yōu)榱薾ull,但是value還沒(méi)有被回收呢。假如,我們使用的是線程池,由于線程池中的線程不會(huì)被釋放,那么這個(gè)線程中對(duì)應(yīng)的ThreadLocalMap也就一直不會(huì)被回收,如果線程很多的話,那么ThreadLocalMap占用的內(nèi)存就越來(lái)越大,這樣的話就可能會(huì)出現(xiàn)內(nèi)存溢出問(wèn)題。

那么,如何解決呢,其實(shí)很簡(jiǎn)單,每次使用完ThreadLocal中保存的參數(shù)后,調(diào)用ThreadLocal的remove方法刪除即可。

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

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