ThreadLocal是什么
ThreadLocal是一個(gè)本地線程副本變量工具類。主要用于將私有線程和該線程存放的副本對(duì)象做一個(gè)映射,各個(gè)線程之間的變量互不干擾,在高并發(fā)場(chǎng)景下,可以實(shí)現(xiàn)無狀態(tài)的調(diào)用,特別適用于各個(gè)線程依賴不通的變量值完成操作的場(chǎng)景
ThreadLoacl數(shù)據(jù)結(jié)構(gòu)
通過上圖可以看出
- 每一個(gè)線程都維護(hù)這一個(gè)ThreadLocalMap的數(shù)組,這個(gè)就是實(shí)現(xiàn)線程之間互不影響的原因,因?yàn)槊總€(gè)線程自己維護(hù)了一個(gè)Entry。
- ThreadLocal只是用來管理每個(gè)線程中Entry的一個(gè)工具,因?yàn)檎嬲腡hreadLocalMap是定義在每一個(gè)線程中,可以通過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獲取到當(dāng)前線程
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
#設(shè)置時(shí)候,ThreadLocalMap的key值就是threadLocal,所以后面可以通過ThreadLocal獲取到這個(gè)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);
}
- 為啥是一個(gè)Entry數(shù)組?因?yàn)榭赡苣愣x了多個(gè)ThreadLocal變量,每個(gè)ThreadLocal指向一個(gè)Entry,這樣不同的ThreadLocal可以操作不同的數(shù)據(jù)
public class ThreadLocalTest {
static ThreadLocal LOCAL = new ThreadLocal();
static ThreadLocal LOCAL2 = new ThreadLocal();
}
- ThreadLocalMap如何解決Hash沖突?ThreadLocalMap是定義再ThreadLocal類中的一個(gè)數(shù)據(jù)結(jié)構(gòu),他采用的是線性探測(cè)的方式而非像HashMap采用鏈表的模式。所謂線性探測(cè),就是根據(jù)初始key的hashcode值確定元素在table數(shù)組中的位置,如果發(fā)現(xiàn)這個(gè)位置上已經(jīng)有其他key值的元素被占用,則利用固定的算法尋找一定步長(zhǎng)的下個(gè)位置,依次判斷,直至找到能夠存放的位置。基于這特點(diǎn),建議最好Entry[]的數(shù)量不要太多,所以這里引出的良好建議是:每個(gè)線程只存一個(gè)變量,這樣的話所有的線程存放到map中的Key都是相同的ThreadLocal,如果一個(gè)線程要保存多個(gè)變量,就需要?jiǎng)?chuàng)建多個(gè)ThreadLocal,多個(gè)ThreadLocal放入Map中時(shí)會(huì)極大的增加Hash沖突的可能
- 從結(jié)構(gòu)圖可以看出,用ThreadLocal時(shí)候set進(jìn)去的對(duì)象必須是每個(gè)線程單獨(dú)new出來的,例如先new了一個(gè)對(duì)象X,然后后面多線程里面set(X),這樣其實(shí)每一個(gè)線程中Entry的Value指向的是同一個(gè)對(duì)象X,這樣會(huì)有問題
ThreadLocal內(nèi)存泄露
為什么會(huì)內(nèi)存泄漏?
ThreadLocal在ThreadLocalMap中是以一個(gè)弱引用身份被Entry中的Key引用的,因此如果ThreadLocal沒有外部強(qiáng)引用來引用它,那么ThreadLocal會(huì)在下次JVM垃圾收集時(shí)被回收。這個(gè)時(shí)候就會(huì)出現(xiàn)Entry中Key已經(jīng)被回收,出現(xiàn)一個(gè)null Key的情況,外部讀取ThreadLocalMap中的元素是無法通過null Key來找到Value的。因此如果當(dāng)前線程的生命周期很長(zhǎng),一直存在,那么其內(nèi)部的ThreadLocalMap對(duì)象也一直生存下來,這些null key就存在一條強(qiáng)引用鏈的關(guān)系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,這條強(qiáng)引用鏈會(huì)導(dǎo)致Entry不會(huì)回收,Value也不會(huì)回收,但Entry中的Key卻已經(jīng)被回收的情況,造成內(nèi)存泄漏
從上可以看出導(dǎo)致內(nèi)存泄露的幾個(gè)原因:
- 使用static的ThreadLocal,延長(zhǎng)了ThreadLocal的生命周期,可能導(dǎo)致的內(nèi)存泄漏
- 分配使用了ThreadLocal又不再調(diào)用get()、set()、remove()方法,那么就會(huì)導(dǎo)致內(nèi)存泄漏
- 由于ThreadLocalMap的生命周期跟Thread一樣長(zhǎng),如果沒有手動(dòng)刪除對(duì)應(yīng)key的value就會(huì)導(dǎo)致內(nèi)存泄漏,而不是因?yàn)槿跻?/li>
解決方法
每次使用完ThreadLocal,都調(diào)用它的remove()方法,清除數(shù)據(jù)。如果一個(gè)線程執(zhí)行時(shí)間過長(zhǎng),代碼上可以將threadlocal用完后馬上remove掉,防止后面代碼執(zhí)行過程導(dǎo)致value值一直得不到釋放