1.目的
ThreadLocal目的是保存一些線程級(jí)別的全局變量,比如connection,或者事務(wù)上下文,避免這些值需要一直通過函數(shù)參數(shù)的方式一路傳遞。
2. 常見用法
舉例其中一種常見用法:
public class Test2 {
public static void main(String[] args) throws InterruptedException {
testThreadLocal();
}
private static void testThreadLocal() {
Util.setGlobalName("zili.dengzl");
new Foo().printName();
}
}
class Foo{
public void printName(){
System.out.println("globalName="+Util.getGlobalName());
}
}
class Util {
private static final ThreadLocal<String> globalName = new ThreadLocal<String>();
public static String getGlobalName() {
return globalName.get();
}
public static void setGlobalName(String name) {
globalName.set(name);
}
}
3.實(shí)現(xiàn)分析
要實(shí)現(xiàn)上面這樣的功能,最簡(jiǎn)單的想法是用一個(gè)Map<Thread,T>,如下:
class MockThreadLocal<T> {
private Map<Thread, T> map = new HashMap<Thread, T>();
public T get() {
return (T) map.get(Thread.currentThread());
}
public void set(T value) {
map.put(Thread.currentThread(), value);
}
}
這樣也能實(shí)現(xiàn)ThreadLocal的效果,但是有一個(gè)問題,當(dāng)對(duì)應(yīng)的線程消失后,map中對(duì)應(yīng)的線程值并不會(huì)被回收,從而造成內(nèi)存泄露。
事實(shí)上ThreadLocal是這樣做的:
每個(gè)Thread都有一個(gè)threadLocalMap,key是threadLocal對(duì)象,value是具體使用的值。ThreadLocal對(duì)象的get就是先取得當(dāng)前的Thread,然后從這個(gè)Thread的threadLcoalMap中取出值。set類似。
下面看下具體代碼:
/**
* 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)
return (T)e.value;
}
return setInitialValue();
}
注意這里如果取到?jīng)]有該線程對(duì)應(yīng)的值,會(huì)調(diào)用setInitialValue();,最終調(diào)用initialValue()生成一個(gè)值,這也是我們很多場(chǎng)景下要override這個(gè)方法的原因;
下面看一下getMap(Thread t)方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
在Thread類中:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
由此可見,所有的ThreadLocal的信息,最終是關(guān)聯(lián)到Thread上的,線程消失后,對(duì)應(yīng)的Thread對(duì)象也被回收,這時(shí)對(duì)應(yīng)的ThreadLocal對(duì)象(該線程部分)也會(huì)被回收。
這里為什么是一個(gè)ThreadLocalMap呢,因?yàn)橐粋€(gè)線程可以有多個(gè)ThreadLocal變量,通過map.getEntry(this)取得對(duì)應(yīng)的某個(gè)具體的變量。
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);
}
最后要注意的一點(diǎn)是,ThreadLocalMap的Entry是一個(gè)weakReference:
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
這里主要因?yàn)門hreadLocalMap的key是ThreadLocal對(duì)象,如果某個(gè)ThreadLocal對(duì)象所有的強(qiáng)引用沒有了,會(huì)利用weakref的功能把他回收掉,然后復(fù)用這個(gè)entry。
考慮一下如果不用weakReference會(huì)出現(xiàn)什么情況:假設(shè)某個(gè)對(duì)象是這樣引用的
private final ThreadLocal<String> globalName = new ThreadLocal<String>();
注意沒有static,然后這個(gè)對(duì)象被不斷的new出來,然后死掉,每次ThreadLocalmap中都會(huì)多出一個(gè)entry,然后這個(gè)entry強(qiáng)引用一個(gè)ThreadLocal對(duì)象,ThreadLocalMap本身就沒有辦法確定哪個(gè)entry是不用了的,如果恰好這個(gè)線程是線程池中的,會(huì)存活很久,那就杯具了。
ThreadLocalMap用了weakReference,失去強(qiáng)引用的ThreadLocal對(duì)象會(huì)在下次gc時(shí)被回收,然后ThreadLocalMap本身在get和set的時(shí)候會(huì)考察key為空的Entry,并復(fù)用它或者清除,從而避免內(nèi)存泄露。
這樣看來,HashMap也有一樣的問題,但為什么hashMap不這樣呢,因?yàn)閔ashMap的put是業(yè)務(wù)代碼操作的,因此如果有長期存活的HashMap,(比如static的)業(yè)務(wù)代碼put進(jìn)去就有義務(wù)去remove,但ThreadLocal的put操作時(shí)ThreadLocal類干的,業(yè)務(wù)代碼不知道,因此也不會(huì)去做remove,而ThreadLocalMap本身不知道引用他的某個(gè)entry的key的對(duì)象什么時(shí)候死掉了,那么如果不用弱引用,就不知道這個(gè)ThreadLocal對(duì)象什么時(shí)候需要回收了。
附:
這里補(bǔ)充一下weakReference的用法供參考(當(dāng)強(qiáng)引用不存在時(shí),下次垃圾回收會(huì)回收弱引用所引用的對(duì)象):
Object o = new Object();
WeakReference<Object> ref = new WeakReference<Object>(o);
System.out.println(ref.get());
o=null;
System.gc();
System.out.println(ref.get());
結(jié)果輸出:
java.lang.Object@de6ced
null
4. FAQ
4.1 為什么一般的ThreadLocal用法都要加static,如下:
class Test {
private static final ThreadLocal<String> globalName = new ThreadLocal<String>();
}
answer:事實(shí)上,不一定是要static,但使用它的對(duì)象在業(yè)務(wù)需要范圍類一定要是單例。因?yàn)楦鶕?jù)前面的分析,ThreadLocalMap是以ThreadLocal對(duì)象為key的,如果Test類不是static,也不是單例的,那么兩個(gè)Test對(duì)象就有兩個(gè)key,取出來的數(shù)據(jù)肯定不同
class TestThreadLocal{
public static void main(String[] args) {
Test t1 = new Test();
Test t2 = new Test();
t1.pool.set("a");
System.out.println(t1.pool.get());
System.out.println(t2.pool.get());
}
}
class Test{
public ThreadLocal pool = new ThreadLocal();
}
輸出將會(huì)是:a,null
原因就無需多解釋了。唯一需要啰嗦的一點(diǎn)是,就算一般情況都是單例,上面那個(gè)weakreference還是必要的,因?yàn)樽鳛榭蚣艽a,不能保證正常使用的情況下一個(gè)線程有很多ThreadLocal,如果不用weakreference,就會(huì)有內(nèi)存泄漏的風(fēng)險(xiǎn),特別是針對(duì)線程池中的線程。