簡介:本文以一個(gè)簡要的代碼示例介紹ThreadLocal類的基本使用,,在此基礎(chǔ)上結(jié)合圖片闡述它的內(nèi)部工作原理,,最后分析了ThreadLocal的內(nèi)存泄露問題以及解決方法。 歡迎探討,,如有錯(cuò)誤敬請(qǐng)指正 如需轉(zhuǎn)載,,請(qǐng)注明出處 http://www.cnblogs.com/nullzx/ 1. ThreadLocal<T> 簡介和使用示例ThreadLocal只有一個(gè)無參的構(gòu)造方法
ThreadLocal的相關(guān)方法
initialValue方法的訪問修飾符是protected,該方法為第一次調(diào)用get方法提供一個(gè)初始值,。默認(rèn)情況下,,第一次調(diào)用get方法返回值null。在使用時(shí),,我們一般會(huì)復(fù)寫ThreadLocal的initialValue方法,使第一次調(diào)用get方法時(shí)返回一個(gè)我們?cè)O(shè)定的初始值,。 下面是一個(gè)ThreadLocal的一個(gè)簡單使用示例
運(yùn)行結(jié)果
從運(yùn)行結(jié)果可以看出,,每個(gè)線程第一次調(diào)用TheadLocal對(duì)象的get方法時(shí)都得到初始值3,注意我們上面的代碼是讓三個(gè)線程順序執(zhí)行,,顯然從運(yùn)行結(jié)果看,,pool-1-thread-1線程結(jié)束后設(shè)置的新值,對(duì)pool-1-thread-3線程是沒有影響的,,pool-1-thread-3線程完成后設(shè)置的新值對(duì)pool-1-thread-2線程也沒有影響,。這就仿佛把ThreadLocal對(duì)象當(dāng)做每個(gè)線程內(nèi)部的對(duì)象一樣,但實(shí)際上tlA對(duì)象是個(gè)外部類對(duì)象,,內(nèi)部類Worker訪問到的是同一個(gè)tlA對(duì)象,,也就是說是被各個(gè)線程共享的。這是如何做到的呢,?我們現(xiàn)在就來看看ThreadLocal對(duì)象的內(nèi)部原理,。 2. ThreadLocal<T>的原理首先,在Thread類中定義了一個(gè)threadLocals,,它是ThreadLocal.ThreadLocalMap對(duì)象的引用,默認(rèn)值是null,。ThreadLocal.ThreadLocalMap對(duì)象表示了一個(gè)以開放地址形式的散列表。當(dāng)我們?cè)诰€程的run方法中第一次調(diào)用ThreadLocal對(duì)象的get方法時(shí),,會(huì)為當(dāng)前線程創(chuàng)建一個(gè)ThreadLocalMap對(duì)象,。也就是每個(gè)線程都各自有一張獨(dú)立的散列表,以ThreadLocal對(duì)象作為散列表的key,,set方法中的值作為value(第一次調(diào)用get方法時(shí),,以initialValue方法的返回值作為value)。顯然我們可以定義多個(gè)ThreadLocal對(duì)象,,而我們一般將ThreadLocal對(duì)象定義為static類型或者外部類中,。上面所表達(dá)的意思就是,相同的key在不同的散列表中的值必然是獨(dú)立的,,每個(gè)線程都是在各自的散列表中執(zhí)行操作,。 TheadLocal中的get源代碼
3. 由ThreadLocal造成的內(nèi)存泄露和相應(yīng)解決辦法ThreadLocalMap 中用內(nèi)部靜態(tài)類Entry表示了散列表中的每一個(gè)條目,下面是它的代碼
可以看出Entry類繼承了WeakRefrence類,,所以一個(gè)條目就是一個(gè)弱引用類型的對(duì)象(要搞清楚,,持有weakRefrence對(duì)象的引用是個(gè)強(qiáng)引用),,那么這個(gè)weakRefrence對(duì)象保存了誰的弱引用呢?我們看到構(gòu)造函數(shù)中有個(gè)supe(k),,k是ThreadLocal類型對(duì)象,,super表示是調(diào)用父類的構(gòu)造函數(shù)(父類是誰你要想清楚哦?),,所以說一個(gè)entry對(duì)象中存儲(chǔ)了ThreadLocal對(duì)象的弱引用和這個(gè)ThreadLocal對(duì)應(yīng)的value對(duì)象的強(qiáng)引用,。有關(guān)弱引用的相關(guān)內(nèi)容請(qǐng)參考我的另一篇博客《Java中的四種引用以及ReferenceQueue和WeakHashMap的使用示例》。 我們現(xiàn)在假設(shè)一種情況,,假設(shè)我們?cè)诰€程的run方法中調(diào)用了一個(gè)方法,,并在這個(gè)方法中創(chuàng)建了ThreadLocal對(duì)象,并使用了他,,內(nèi)存結(jié)構(gòu)示意圖如下,。當(dāng)這個(gè)方法結(jié)束時(shí),這個(gè)方法中創(chuàng)建的ThreadLocal對(duì)象本身(圖中綠色區(qū)域)就被垃圾回收器回收了,,但是線程還沒有結(jié)束,,所以ThreadLocalMap中還存在這個(gè)entry。由于entry中的key(即ThreadLocal對(duì)象)是弱引用類型,,所以此時(shí)調(diào)用entry.get()方法時(shí)就會(huì)返回null,,內(nèi)部結(jié)構(gòu)如下圖所示。 從圖中我們可以看到value對(duì)象(紅色區(qū)域)始終不能被回收,,而我們?cè)僖膊粫?huì)使用它了,,這就造成了內(nèi)存泄露。 那Entry中為什么保存的是key的弱引用呢,?其實(shí)這是為了最大程度上減少內(nèi)存泄露,,副作用是同時(shí)減少哈希表中的沖突。當(dāng)ThreadLocal對(duì)象被回收時(shí),,對(duì)應(yīng)entry中的key就自動(dòng)變成null(entry對(duì)象本身不為null),。若此后我們調(diào)用get,set或remove方法時(shí),就會(huì)嘗試刪除key為null的entry,,以釋放value對(duì)象所占用的內(nèi)存,。 我們現(xiàn)在來看看get方法(上面有g(shù)et方法的源代碼)中調(diào)用的getEntry方法。
從源代碼中我們可以看出,,有可能會(huì)調(diào)用getEntryAfterMiss方法,,而在這個(gè)方法中,刪除key為null的Entry對(duì)象,。同理set方法也有類似的行為,而remove方法不僅僅刪除掉參數(shù)ThreadLocal對(duì)象對(duì)應(yīng)的entry,,而且也會(huì)嘗試刪除其它key為null的entry。
但是上述的方式并不能完全解決內(nèi)存泄露問題,,因?yàn)槲覀冊(cè)谶@個(gè)方法結(jié)束的時(shí)候邏輯上不一定必須調(diào)用get方法,,而get方法也不一定執(zhí)行g(shù)etEntryAfterMiss方法,。所以類本身是沒有這個(gè)能力的,我們只能在不再使用某個(gè)ThreadLocal對(duì)象后,,手動(dòng)調(diào)用remoev方法來刪除它,各自線程中調(diào)用共享的ThreadLocal對(duì)象的remove方法,,這對(duì)其它線程是沒有影響的,這個(gè)應(yīng)該不難理解,。在線程池中這就操作是必須的,,不僅僅是內(nèi)存泄露的問題。因?yàn)榫€程池中的線程是重復(fù)使用的,,意味著這個(gè)線程的ThreadLocalMap對(duì)象也是重復(fù)使用的,如果我們不手動(dòng)調(diào)用remove方法,,那么后面的線程就有可能獲取到上個(gè)線程遺留下來的value值,,造成bug。 4. 參考內(nèi)容 [1]. Java并發(fā)編程:深入剖析ThreadLocal |
|