1.目的
ThreadLocal目的是保存一些線程級別的全局變量,比如connection,或者事務上下文,避免這些值需要一直通過函數參數的方式一路傳遞。
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.實現(xiàn)分析
要實現(xiàn)上面這樣的功能,最簡單的想法是用一個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);
}
}
這樣也能實現(xiàn)ThreadLocal的效果,但是有一個問題,當對應的線程消失后,map中對應的線程值并不會被回收,從而造成內存泄露。
事實上ThreadLocal是這樣做的:
每個Thread都有一個threadLocalMap,key是threadLocal對象,value是具體使用的值。ThreadLocal對象的get就是先取得當前的Thread,然后從這個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();
}
注意這里如果取到沒有該線程對應的值,會調用setInitialValue();,最終調用initialValue()生成一個值,這也是我們很多場景下要override這個方法的原因;
下面看一下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的信息,最終是關聯(lián)到Thread上的,線程消失后,對應的Thread對象也被回收,這時對應的ThreadLocal對象(該線程部分)也會被回收。
這里為什么是一個ThreadLocalMap呢,因為一個線程可以有多個ThreadLocal變量,通過map.getEntry(this)取得對應的某個具體的變量。
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);
}
最后要注意的一點是,ThreadLocalMap的Entry是一個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;
}
}
這里主要因為ThreadLocalMap的key是ThreadLocal對象,如果某個ThreadLocal對象所有的強引用沒有了,會利用weakref的功能把他回收掉,然后復用這個entry。
考慮一下如果不用weakReference會出現(xiàn)什么情況:假設某個對象是這樣引用的
private final ThreadLocal<String> globalName = new ThreadLocal<String>();
注意沒有static,然后這個對象被不斷的new出來,然后死掉,每次ThreadLocalmap中都會多出一個entry,然后這個entry強引用一個ThreadLocal對象,ThreadLocalMap本身就沒有辦法確定哪個entry是不用了的,如果恰好這個線程是線程池中的,會存活很久,那就杯具了。
ThreadLocalMap用了weakReference,失去強引用的ThreadLocal對象會在下次gc時被回收,然后ThreadLocalMap本身在get和set的時候會考察key為空的Entry,并復用它或者清除,從而避免內存泄露。
這樣看來,HashMap也有一樣的問題,但為什么hashMap不這樣呢,因為hashMap的put是業(yè)務代碼操作的,因此如果有長期存活的HashMap,(比如static的)業(yè)務代碼put進去就有義務去remove,但ThreadLocal的put操作時ThreadLocal類干的,業(yè)務代碼不知道,因此也不會去做remove,而ThreadLocalMap本身不知道引用他的某個entry的key的對象什么時候死掉了,那么如果不用弱引用,就不知道這個ThreadLocal對象什么時候需要回收了。
附:
這里補充一下weakReference的用法供參考(當強引用不存在時,下次垃圾回收會回收弱引用所引用的對象):
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());
結果輸出:
java.lang.Object@de6ced
null
4. FAQ
4.1 為什么一般的ThreadLocal用法都要加static,如下:
class Test {
private static final ThreadLocal<String> globalName = new ThreadLocal<String>();
}
answer:事實上,不一定是要static,但使用它的對象在業(yè)務需要范圍類一定要是單例。因為根據前面的分析,ThreadLocalMap是以ThreadLocal對象為key的,如果Test類不是static,也不是單例的,那么兩個Test對象就有兩個key,取出來的數據肯定不同
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();
}
輸出將會是:a,null
原因就無需多解釋了。唯一需要啰嗦的一點是,就算一般情況都是單例,上面那個weakreference還是必要的,因為作為框架代碼,不能保證正常使用的情況下一個線程有很多ThreadLocal,如果不用weakreference,就會有內存泄漏的風險,特別是針對線程池中的線程。