最近在重構(gòu)代碼時(shí)淆两,發(fā)現(xiàn)有不同類(lèi)之間參數(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線(xiàn)程互捌,每個(gè)Thread線(xiàn)程又有一個(gè)ThreadLocalMap潘明,通過(guò)ThreadLocal類(lèi)的set方法將需要使用的參數(shù)保存在ThreadLocalMap這個(gè)map中,后面只要在同一個(gè)線(xiàn)程中利用ThreadLocal實(shí)例的get方法就可以得到之前設(shè)置的參數(shù)秕噪。
其實(shí)钳降,在看源碼之前,可以簡(jiǎn)單思考下實(shí)現(xiàn)的套路無(wú)非就是:
1. 獲取當(dāng)前線(xiàn)程腌巾。
2. 獲取此線(xiàn)程的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類(lèi),這個(gè)類(lèi)的作用主要就是使某個(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)有被回收呢岸夯。假如麻献,我們使用的是線(xiàn)程池,由于線(xiàn)程池中的線(xiàn)程不會(huì)被釋放猜扮,那么這個(gè)線(xiàn)程中對(duì)應(yīng)的ThreadLocalMap也就一直不會(huì)被回收勉吻,如果線(xiàn)程很多的話(huà),那么ThreadLocalMap占用的內(nèi)存就越來(lái)越大旅赢,這樣的話(huà)就可能會(huì)出現(xiàn)內(nèi)存溢出問(wèn)題齿桃。
那么惑惶,如何解決呢,其實(shí)很簡(jiǎn)單源譬,每次使用完ThreadLocal中保存的參數(shù)后集惋,調(diào)用ThreadLocal的remove方法刪除即可。