1 什么是ThreadLocal
ThreadLocal是什么呢残黑?在實(shí)際開發(fā)中經(jīng)常被用來綁定用戶信息馍佑、日志號(hào)。數(shù)據(jù)庫連接等等梨水。這樣一來拭荤,我們編碼時(shí)就不用通過傳遞參數(shù)方式而影響業(yè)務(wù)邏輯。就如名字一般疫诽,我們可以簡(jiǎn)單的認(rèn)為它的作用就是把數(shù)據(jù)綁定到當(dāng)前線程上舅世,然后用于后續(xù)的操作。
既然是將數(shù)據(jù)綁定到當(dāng)前線程上奇徒,那最方便高效的數(shù)據(jù)存儲(chǔ)方式就是key-value的hash方式存儲(chǔ)了雏亚。不過不同于HashMap的實(shí)現(xiàn)方式,它單獨(dú)提供了一個(gè)叫做ThreadLocalMap的Map類摩钙,與HashMap有著類似的功能罢低,但是區(qū)別是它的KEY使用弱引用(只要GC掃描到,只有弱引用的情況下就會(huì)被回收)胖笛。
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;
}
}
}
2 ThreadLocalMap的KEY為什么設(shè)計(jì)成弱引用
為什么ThreadLocalMap和HashMap的key不同网持,會(huì)設(shè)計(jì)成弱引用呢?我們來分析一下:
HashMap被程序員使用存儲(chǔ)各類數(shù)據(jù)长踊,ThreadLocalMap為靜態(tài)訪問修飾符為Default的類功舀,只為線程Thread存儲(chǔ)數(shù)據(jù)(其屬性)。
看一下類注釋:To help deal withvery large and long-lived usages, the hash table entries use WeakReferences for keys身弊。解釋一下:為了幫助處理巨大和長(zhǎng)時(shí)間存活的對(duì)象使用辟汰,才會(huì)使用弱引用。
所以總結(jié)上面兩點(diǎn)阱佛,我們就知道莉擒。ThreadLocalMap不像HashMap一樣被外部使用,可以認(rèn)為是線程私有的Map,這就意味著:在線程長(zhǎng)時(shí)間存活的情況下瘫絮,如果ThreadLocalMap沒有使用弱引用涨冀,而是使用HashMap的話。當(dāng)Map中被放入大量大對(duì)象和值時(shí)麦萤,又不及時(shí)手動(dòng)刪除K-V的話就很可能會(huì)出現(xiàn)應(yīng)用堆棧溢出的情況鹿鳖。但是如果使用弱引用的話扁眯,那么在沒有其他強(qiáng)引用的時(shí)候,就不需要程序員手動(dòng)去刪除K-V,再一定程度上會(huì)降低堆棧溢出的風(fēng)險(xiǎn)(要是KEY都被外部強(qiáng)引用翅帜,那也沒辦法耙鎏础)。
3 ThreadLocal為什么內(nèi)存泄露
由于使用了弱引用涝滴,就有可能造成網(wǎng)上經(jīng)常說的內(nèi)存泄露绣版?(其實(shí)感覺沒有恐怖)我們先說這個(gè)內(nèi)存泄漏是怎么產(chǎn)生的呢?如下圖:
圖上我們可以看到當(dāng)ThreadLocal的實(shí)例如果設(shè)置為null,那么之后實(shí)例會(huì)被回收歼疮。這個(gè)時(shí)候ThreadLocal,也就是ThreadLocalMap的key僅有一個(gè)弱引用了杂抽,說明GC時(shí)KEY會(huì)被回收。當(dāng)回收后我們就會(huì)發(fā)現(xiàn)V這個(gè)值就被留在了Map當(dāng)中了韩脏,我們無法獲取缩麸,也無法刪除。這就是所謂的內(nèi)存泄露問題赡矢。
不過我們也發(fā)現(xiàn)只要線程銷毀后杭朱,ThreadLocalMap也會(huì)被回收就解決了線程泄露的問題。但是如果線程長(zhǎng)時(shí)間存活那就麻煩了吹散。還有一種情況就是在使用線程池的時(shí)候弧械。我們都知道線程池里的線程都是復(fù)用的,那么當(dāng)設(shè)置了ThreadLocal的線程沒有清除之前設(shè)置數(shù)據(jù)的話空民,就很可能造成之后復(fù)用線程的時(shí)候使用錯(cuò)誤數(shù)據(jù)梦谜。所以,ThreadLocal類提供了一個(gè)解綁數(shù)據(jù)的方法Remove方法袭景。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
4 總結(jié)
ThreadLocal 方便編程時(shí)將數(shù)據(jù)綁定到當(dāng)前線程上唁桩,而不用方法傳遞參數(shù),只需在需要使用時(shí)從ThreadLocal獲取數(shù)據(jù)即可耸棒。ThreadLocal中的ThreadLocalMap的KEY使用了弱引用荒澡,方便線程在長(zhǎng)時(shí)間存活的情況下,及時(shí)清理GC只有弱引用的KEY值与殃,一定程度上降低堆棧溢出的風(fēng)險(xiǎn)单山。但同時(shí)由于弱引用的使用,帶來了線程泄露的風(fēng)險(xiǎn)幅疼,以及在數(shù)據(jù)庫線程池場(chǎng)景下使用造成數(shù)據(jù)錯(cuò)誤的風(fēng)險(xiǎn)米奸。這就要求每個(gè)程序員在使用ThreadLocal結(jié)束后,及時(shí)使用remove方法(即使有些地方不remove也沒有風(fēng)險(xiǎn)爽篷,但是寫了就不會(huì)考慮這考慮那了悴晰,畢竟還有很多代碼等著你)。