1、簡(jiǎn)介
ThreadLocal是什么呢?其實(shí)ThreadLocal并非是一個(gè)線程的本地實(shí)現(xiàn)版本,它并不是一個(gè)Thread,而是threadlocalvariable(線程局部變量)。也許把它命名為T(mén)hreadLocalVar更加合適。線程局部變量(ThreadLocal)其實(shí)的功用非常簡(jiǎn)單,就是為每一個(gè)使用該變量的線程都提供一個(gè)變量值的副本,是Java中一種較為特殊的線程綁定機(jī)制,是每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)和其它線程的副本沖突。
2、Spring中應(yīng)用
Spring使用ThreadLocal解決線程安全問(wèn)題。一般情況下,只有無(wú)狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因?yàn)镾pring對(duì)一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態(tài)采用ThreadLocal進(jìn)行處理,讓它們也成為線程安全的狀態(tài),因?yàn)橛袪顟B(tài)的Bean就可以在多線程中共享了。
3、Slf4j 日志輸出中的應(yīng)用
Java Web項(xiàng)目中,通常使用實(shí)現(xiàn)了 Slf4j 的Logback或Log4j來(lái)進(jìn)行日志輸出,Slf4j 中定義了 MDC 接口,要求實(shí)現(xiàn)多線程間日志隔離,Logback 和 Log4j 正是利用ThreadLocal來(lái)實(shí)現(xiàn)的。更多內(nèi)容將會(huì)在另一篇專(zhuān)門(mén)介紹MDC的文章中講解。
4、實(shí)現(xiàn)原理
ThreadLocal起作用的根源是Thread類(lèi):
public class Thread implements Runnable {
......(其他源碼)
/*
* 當(dāng)前線程的ThreadLocalMap,主要存儲(chǔ)該線程自身的ThreadLocal
* 本文主要討論的就是這個(gè)ThreadLocalMap
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal,自父線程集成而來(lái)的ThreadLocalMap,
* 主要用于父子線程間ThreadLocal變量的傳遞
* 此處我們不過(guò)多解釋inheritableThreadLocals變量
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
......(其他源碼)
}
正是由于Thread中有 ThreadLocal.ThreadLocalMap 變量,ThreadLocal才得以使用。
下面來(lái)看一下ThreadLocal工作原理。
ThreadLocal的set()方法的源碼是:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
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è)方法內(nèi)部我們看到,首先通過(guò)getMap(Thread t)方法獲取一個(gè)和當(dāng)前線程相關(guān)的ThreadLocalMap,然后將變量的值設(shè)置到這個(gè)ThreadLocalMap對(duì)象中,當(dāng)然如果獲取到的ThreadLocalMap對(duì)象為空,就通過(guò)createMap方法創(chuàng)建。
線程隔離的秘密,就在于ThreadLocalMap這個(gè)類(lèi)。ThreadLocalMap是ThreadLocal類(lèi)的一個(gè)靜態(tài)內(nèi)部類(lèi),它實(shí)現(xiàn)了鍵值對(duì)的設(shè)置和獲取(對(duì)比Map對(duì)象來(lái)理解),每個(gè)線程中都有一個(gè)獨(dú)立的ThreadLocalMap副本,它所存儲(chǔ)的值,只能被當(dāng)前線程讀取和修改。ThreadLocal類(lèi)通過(guò)操作每一個(gè)線程特有的ThreadLocalMap副本,從而實(shí)現(xiàn)了變量訪問(wèn)在不同線程中的隔離。因?yàn)槊總€(gè)線程的變量都是自己特有的,完全不會(huì)有并發(fā)錯(cuò)誤。還有一點(diǎn)就是,ThreadLocalMap存儲(chǔ)的鍵值對(duì)中的鍵是this對(duì)象指向的ThreadLocal對(duì)象,而值就是你所設(shè)置的對(duì)象了。
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
再看一下ThreadLocal類(lèi)中的get()方法:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}
再來(lái)看setInitialValue()方法:
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
獲取和當(dāng)前線程綁定的值時(shí),ThreadLocalMap對(duì)象是以this指向的ThreadLocal對(duì)象為鍵進(jìn)行查找的,這當(dāng)然和前面set()方法的代碼是相呼應(yīng)的。
5、內(nèi)存泄漏

Threadlocal里面使用了一個(gè)存在弱引用的map,當(dāng)釋放掉threadlocal的強(qiáng)引用以后,map里面的value卻沒(méi)有被回收.而這塊value永遠(yuǎn)不會(huì)被訪問(wèn)到了. 所以存在著內(nèi)存泄露.
5.1、為什么使用弱引用
要理解為什么ThreadLocalMap中需要使用WeakReference作為key類(lèi)型,那么首先需要理解WeakReference的意義。
WeakReference是Java語(yǔ)言規(guī)范中為了區(qū)別直接的對(duì)象引用(程序中通過(guò)構(gòu)造函數(shù)聲明出來(lái)的對(duì)象引用)而定義的另外一種引用關(guān)系。WeakReference標(biāo)志性的特點(diǎn)是:reference實(shí)例不會(huì)影響到被應(yīng)用對(duì)象的GC回收行為(即只要對(duì)象被除WeakReference對(duì)象之外所有的對(duì)象解除引用后,該對(duì)象便可以被GC回收),只不過(guò)在被對(duì)象回收之后,reference實(shí)例想獲得被應(yīng)用的對(duì)象時(shí)程序會(huì)返回null。
If you have a ThreadLocal as a final class member, that's a strong reference, and it cannot be collected until the class is unloaded. But this is how any class member works, and isn't considered a memory leak.
理解了WeakReference之后,ThreadLocalMap使用它的目的也相對(duì)清晰了:當(dāng)threadLocal實(shí)例可以被GC回收時(shí),系統(tǒng)可以檢測(cè)到該threadLocal對(duì)應(yīng)的Entry是否已經(jīng)過(guò)期(根據(jù)reference.get() == null來(lái)判斷,如果為true則表示過(guò)期,程序內(nèi)部稱為stale slots)來(lái)自動(dòng)做一些清除工作,否則如果不清除的話容易產(chǎn)生內(nèi)存無(wú)法釋放的問(wèn)題:value對(duì)應(yīng)的對(duì)象即使不再使用,但由于被threadLocalMap所引用導(dǎo)致無(wú)法被GC回收。
5.2、最佳實(shí)踐
ThreadLocalMap會(huì)在set,get以及resize等方法中對(duì)stale slots做自動(dòng)刪除(set以及get不保證所有過(guò)期slots會(huì)在操作中會(huì)被刪除,而resize則會(huì)刪除threadLocalMap中所有的過(guò)期slots)。
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*/
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);//此處著手清除key為null的Entry
}
最好的做法是將調(diào)用threadlocal的remove方法:把當(dāng)前ThreadLocal從當(dāng)前線程的ThreadLocalMap中移除。(包括key,value)
6、總結(jié)
ThreadLocal使用場(chǎng)合主要解決多線程中數(shù)據(jù)數(shù)據(jù)因并發(fā)產(chǎn)生不一致問(wèn)題。ThreadLocal為每個(gè)線程的中并發(fā)訪問(wèn)的數(shù)據(jù)提供一個(gè)副本,通過(guò)訪問(wèn)副本來(lái)運(yùn)行業(yè)務(wù),這樣的結(jié)果是耗費(fèi)了內(nèi)存,單大大減少了線程同步所帶來(lái)性能消耗,也減少了線程并發(fā)控制的復(fù)雜度。
ThreadLocal不能使用基本數(shù)據(jù)類(lèi)型,只能使用Object類(lèi)型。