一文搞懂ThreadLocal及相关的内存泄露问题
首先,看一張整體的結(jié)構(gòu)圖,來(lái)幫助理解
什么是ThreadLocal
ThreadLocal用于創(chuàng)建線(xiàn)程局部變量,如果創(chuàng)建一個(gè)ThreadLocal變量,那么訪問(wèn)這個(gè)變量的每個(gè)線(xiàn)程都會(huì)有這個(gè)變量的一個(gè)副本,在實(shí)際多線(xiàn)程操作的時(shí)候,操作的是自己本地內(nèi)存中的變量,從而規(guī)避了線(xiàn)程安全問(wèn)題
ThreadLocal的簡(jiǎn)單使用
package test;public class ThreadLocalTest {static ThreadLocal<String> threadLocal= new ThreadLocal<>();public static void main(String[] args) {Thread t1 = new Thread(()->{//設(shè)置線(xiàn)程1的本地變量的值threadLocal.set("線(xiàn)程1");System.out.println(threadLocal.get());threadLocal.remove();System.out.println("after remove : " + threadLocal.get());});Thread t2 = new Thread(()->{//設(shè)置線(xiàn)程2的本地變量threadLocal.set("線(xiàn)程2");System.out.println(threadLocal.get());threadLocal.remove();System.out.println("after remove : " + threadLocal.get());});t1.start();t2.start();} }輸出結(jié)果:
由此可知,雖然threadLocal為成員變量,即線(xiàn)程共享,但使用threadLocal在不用線(xiàn)程進(jìn)行賦值、獲取等操作時(shí),不同線(xiàn)程的操作互不相擾。
那么ThreadLocal是如何作為成員變量,卻能在實(shí)現(xiàn)多線(xiàn)程下數(shù)據(jù)不共享,作為線(xiàn)程局部變量?
ThreadLocal的原理
回到一開(kāi)始的圖:
- ThreadLocal并不實(shí)際存儲(chǔ)數(shù)據(jù),而是作為一個(gè)工具類(lèi),提供get、set方法根據(jù)當(dāng)前線(xiàn)程用于操作當(dāng)前線(xiàn)程中的實(shí)際數(shù)據(jù)
- 每個(gè)Thread(線(xiàn)程)中都持有一個(gè)ThreadLocal.ThreadLocalMap類(lèi)型的成員變量,初始值為null。ThreadLocal的get、set方法實(shí)際上就是在操作這個(gè)成員變量。
- ThreadLocalMap持有一個(gè)Entry[]類(lèi)型的成員變量table,類(lèi)似JDK1.7的HashMap中的Entry[] table 可以類(lèi)比學(xué)習(xí)
- Entry key為T(mén)hreadLocal<?>類(lèi)型的的弱引用,value為Object類(lèi)型的強(qiáng)引用
ThreadLocal的set()
public void set(T value) {//1、獲取當(dāng)前線(xiàn)程(調(diào)用者線(xiàn)程)Thread t = Thread.currentThread();//2、獲取當(dāng)前線(xiàn)程的ThreadLocalMap變量ThreadLocalMap map = getMap(t);//3、如果map不為null,就直接添加本地變量,key為當(dāng)前定義的ThreadLocal變量的this引用,值為添加的本地變量值if (map != null)map.set(this, value);//4、如果map為null,說(shuō)明首次添加,需要首先創(chuàng)建出對(duì)應(yīng)的mapelsecreateMap(t, value); }ThreadLocalMap getMap(Thread t) {//獲取線(xiàn)程的threadLocalsreturn t.threadLocals; }void createMap(Thread t, T firstValue) {//創(chuàng)建線(xiàn)程的threadLocals, this為T(mén)hreadLocal<?>的引用t.threadLocals = new ThreadLocalMap(this, firstValue); }ThreadLocal的get()
在get方法的實(shí)現(xiàn)中,首先獲取當(dāng)前調(diào)用者線(xiàn)程,如果當(dāng)前線(xiàn)程的threadLocals不為null,就直接返回當(dāng)前線(xiàn)程綁定的本地變量值,否則執(zhí)行setInitialValue方法初始化threadLocals變量。在setInitialValue方法中,類(lèi)似于set方法的實(shí)現(xiàn),都是判斷當(dāng)前線(xiàn)程的threadLocals變量是否為null,是則添加本地變量(這個(gè)時(shí)候由于是初始化,所以添加的值為null),否則創(chuàng)建threadLocals變量,同樣添加的值為null。
引用自:https://www.cnblogs.com/fsmly/p/11020641.html#_label0
ThreadLocal操作總結(jié)
根據(jù)上述源碼的分析可知:ThreadLocal最終操作的都是調(diào)用線(xiàn)程的ThreadLocalMap成員變量,因此不同線(xiàn)程使用同一個(gè)ThreadLocal成員變量互不相擾。
每個(gè)線(xiàn)程內(nèi)部有一個(gè)ThreadLocal.ThreadLocalMap類(lèi)型threadLocals的成員變量,該變量的類(lèi)型為類(lèi)似于HashMap,其中的key為當(dāng)前定義的ThreadLocal變量的this引用,value為使用set方法設(shè)置的值。每個(gè)線(xiàn)程的本地變量存放在自己的本地內(nèi)存變量threadLocals中,如果當(dāng)前線(xiàn)程一直不消亡,那么這些本地變量就會(huì)一直存在(所以可能會(huì)導(dǎo)致內(nèi)存溢出),因此使用完畢需要將其remove掉。
ThreadLocal使用不當(dāng)造成內(nèi)存泄漏問(wèn)題
弱引用
弱引用(這里討論ThreadLocalMap中的Entry類(lèi)的重點(diǎn)):如果一個(gè)對(duì)象只具有弱引用,那么這個(gè)對(duì)象就會(huì)被垃圾回收器GC掉(被弱引用所引用的對(duì)象只能生存到下一次GC之前,當(dāng)發(fā)生GC時(shí)候,無(wú)論當(dāng)前內(nèi)存是否足夠,弱引用所引用的對(duì)象都會(huì)被回收掉)。弱引用也是和一個(gè)引用隊(duì)列聯(lián)合使用,如果弱引用的對(duì)象被垃圾回收期回收掉,JVM會(huì)將這個(gè)引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。若引用的對(duì)象可以通過(guò)弱引用的get方法得到,當(dāng)引用的對(duì)象唄回收掉之后,再調(diào)用get方法就會(huì)返回null
ThreadLocalMap內(nèi)部實(shí)際上是一個(gè)Entry數(shù)組:
- Entry是繼承自WeakReference(弱引用)的一個(gè)類(lèi)。
- Entry的key是指向ThreadLocal的弱引用,value一般為強(qiáng)引用
當(dāng)一個(gè)線(xiàn)程調(diào)用ThreadLocal的set方法設(shè)置變量的時(shí)候,當(dāng)前線(xiàn)程的ThreadLocalMap就會(huì)存放一個(gè)記錄(Entry),這個(gè)記錄的key值為T(mén)hreadLocal的弱引用,value就是通過(guò)set設(shè)置的值。
如果當(dāng)前線(xiàn)程一直存在且沒(méi)有調(diào)用該ThreadLocal的remove方法,此時(shí)存在兩種情況:
- ThreadLocalMap之外存在ThreadLocal的引用:那么當(dāng)前線(xiàn)程中的ThreadLocalMap中會(huì)存在對(duì)ThreadLocal變量的引用和value對(duì)象的引用,無(wú)法進(jìn)行垃圾回收,導(dǎo)致這些本地變量一直存在,可能會(huì)出現(xiàn)內(nèi)存溢出
- ThreadLocalMap之外不存在ThreadLocal的引用:因?yàn)門(mén)hreadLocalMap中的Entry的key使用的是ThreadLocal對(duì)象的弱引用,所以下一次垃圾回收時(shí)ThreadLocal(key)將被回收。此時(shí)ThreadLocalMap中就可能存在key為null但是value不為null的現(xiàn)象,出現(xiàn)內(nèi)存泄漏。
因此每次使用完ThreadLocal,建議調(diào)用它的remove()方法,清除數(shù)據(jù),避免內(nèi)存泄漏
內(nèi)存泄漏問(wèn)題總結(jié)
ThreadLocalMap中的Entry的key使用的是ThreadLocal對(duì)象的弱引用,在沒(méi)有其他地方對(duì)ThreadLocal存在依賴(lài)時(shí),ThreadLocalMap中的ThreadLocal對(duì)象(即key)就會(huì)被回收,而如果其他地方存在ThreadLocal的引用則不會(huì)被回收。當(dāng)key被回收時(shí),Map中就可能存在key為null但是value不為null的現(xiàn)象,出現(xiàn)內(nèi)存泄漏。
因此每次使用完ThreadLocal,建議調(diào)用它的remove()方法,清除數(shù)據(jù),避免內(nèi)存泄漏。
參考
- ThreadLocal
- Java中的ThreadLocal詳解
- TheadLocal 引起的內(nèi)存泄露故障分析
總結(jié)
以上是生活随笔為你收集整理的一文搞懂ThreadLocal及相关的内存泄露问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 华为手机电池不耐用怎么办
- 下一篇: 有序数组中查找第一个比target大的数