ThreadLocal是什么
ThreadLocal是一個本地線程副本變量工具類枫吧。主要用于將私有線程和該線程存放的副本對象做一個映射雳灾,各個線程之間的變量互不干擾,在高并發(fā)場景下季二,可以實現(xiàn)無狀態(tài)的調用伞剑,特別適用于各個線程依賴不通的變量值完成操作的場景
ThreadLoacl數(shù)據(jù)結構
image.png
通過上圖可以看出
- 每一個線程都維護這一個ThreadLocalMap的數(shù)組,這個就是實現(xiàn)線程之間互不影響的原因胆数,因為每個線程自己維護了一個Entry肌蜻。
- ThreadLocal只是用來管理每個線程中Entry的一個工具,因為真正的ThreadLocalMap是定義在每一個線程中,可以通過ThreadLocal的get,set方法明白這一切
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();
}
##通過Thread.currentThread獲取到當前線程
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
#設置時候必尼,ThreadLocalMap的key值就是threadLocal,所以后面可以通過ThreadLocal獲取到這個entry的value值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
- 為啥是一個Entry數(shù)組宋欺?因為可能你定義了多個ThreadLocal變量,每個ThreadLocal指向一個Entry,這樣不同的ThreadLocal可以操作不同的數(shù)據(jù)
public class ThreadLocalTest {
static ThreadLocal LOCAL = new ThreadLocal();
static ThreadLocal LOCAL2 = new ThreadLocal();
}
- ThreadLocalMap如何解決Hash沖突胰伍?ThreadLocalMap是定義再ThreadLocal類中的一個數(shù)據(jù)結構齿诞,他采用的是線性探測的方式而非像HashMap采用鏈表的模式。所謂線性探測骂租,就是根據(jù)初始key的hashcode值確定元素在table數(shù)組中的位置祷杈,如果發(fā)現(xiàn)這個位置上已經(jīng)有其他key值的元素被占用,則利用固定的算法尋找一定步長的下個位置渗饮,依次判斷但汞,直至找到能夠存放的位置。基于這特點互站,建議最好Entry[]的數(shù)量不要太多私蕾,所以這里引出的良好建議是:每個線程只存一個變量,這樣的話所有的線程存放到map中的Key都是相同的ThreadLocal胡桃,如果一個線程要保存多個變量踩叭,就需要創(chuàng)建多個ThreadLocal,多個ThreadLocal放入Map中時會極大的增加Hash沖突的可能
- 從結構圖可以看出翠胰,用ThreadLocal時候set進去的對象必須是每個線程單獨new出來的容贝,例如先new了一個對象X,然后后面多線程里面set(X),這樣其實每一個線程中Entry的Value指向的是同一個對象X之景,這樣會有問題
ThreadLocal內(nèi)存泄露
image.png
為什么會內(nèi)存泄漏?
ThreadLocal在ThreadLocalMap中是以一個弱引用身份被Entry中的Key引用的斤富,因此如果ThreadLocal沒有外部強引用來引用它,那么ThreadLocal會在下次JVM垃圾收集時被回收锻狗。這個時候就會出現(xiàn)Entry中Key已經(jīng)被回收满力,出現(xiàn)一個null Key的情況焕参,外部讀取ThreadLocalMap中的元素是無法通過null Key來找到Value的。因此如果當前線程的生命周期很長油额,一直存在龟糕,那么其內(nèi)部的ThreadLocalMap對象也一直生存下來,這些null key就存在一條強引用鏈的關系一直存在:Thread --> ThreadLocalMap-->Entry-->Value悔耘,這條強引用鏈會導致Entry不會回收,Value也不會回收我擂,但Entry中的Key卻已經(jīng)被回收的情況衬以,造成內(nèi)存泄漏
從上可以看出導致內(nèi)存泄露的幾個原因:
- 使用static的ThreadLocal,延長了ThreadLocal的生命周期校摩,可能導致的內(nèi)存泄漏
- 分配使用了ThreadLocal又不再調用get()看峻、set()、remove()方法衙吩,那么就會導致內(nèi)存泄漏
- 由于ThreadLocalMap的生命周期跟Thread一樣長互妓,如果沒有手動刪除對應key的value就會導致內(nèi)存泄漏,而不是因為弱引用
解決方法
每次使用完ThreadLocal坤塞,都調用它的remove()方法冯勉,清除數(shù)據(jù)。如果一個線程執(zhí)行時間過長摹芙,代碼上可以將threadlocal用完后馬上remove掉灼狰,防止后面代碼執(zhí)行過程導致value值一直得不到釋放