概述
用于同一個線程內(nèi)的方法要共享某些變量或狀態(tài)的時候,提供線程內(nèi)的局部變量,這種變量在線程的生命周期內(nèi)起作用,減少同一個線程內(nèi)多個函數(shù)或者組件之間一些公共變量的傳遞的復(fù)雜度
源碼解讀
源碼的閱讀主要集中在幾個關(guān)鍵方法
構(gòu)造函數(shù)
/**
* Creates a thread local variable.
*/
public ThreadLocal() {
}
可以看出,默認的構(gòu)造函數(shù)什么都沒有干,但如果需要設(shè)置初始值怎么辦
initialValue()
protected T initialValue() {
return null;
}
使用者可以通過繼承ThreadLocal覆蓋該方法來設(shè)置初始值,該值在第一次調(diào)用get()方法時被調(diào)用,該方法在整個ThreadLocal的生命周期中應(yīng)該只對調(diào)用一次,除非用戶顯示地調(diào)用了remove(),然后又調(diào)用get()時會再次調(diào)用initialValue
get()
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();
}
get方法中可以看出先得到當(dāng)前線程的threadlocalmap,如果不存在該map(首次調(diào)用get()),則調(diào)用setInitialValue(),如果存在則得到當(dāng)前Key對應(yīng)的值
setInitialValue()
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;
}
先調(diào)用intialValue得到初始值,然后得到該線程對應(yīng)的ThreadLocalMap,然后在Map中set初始值,如果沒有ThreadLocalMap則創(chuàng)建,并設(shè)置當(dāng)前TheadLocal初始值.從上可以看出,初始化的時候可能做兩件事
1、已有map
則將ThreadLocal作為key,initialValue為value放入到map中
2、沒有map
新建一個ThreadLocalMap,并將<key,value>放入其中
createMap()
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set(T 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);
}
和setInitialValue方法類似
remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
比較重要的一個方法,將當(dāng)前的threadlocal變量從map中移除。
tips
比較重要的一點是,ThreadLocal,Thread,ThreadLocalMap的設(shè)計
目前的設(shè)計是Thread中有ThreadLocalMap,Map中以ThreadLocal為key,這種設(shè)計非常的清晰,由于在ThreadLocalMap中ThreadLocal是以WeakReference的形式存在的,所以其引用鏈或如下所示,也會產(chǎn)生GC疑問:ThreadLocal被回收,但是map中的entry一直不能回收的問題。
所以引出了最佳實踐問題
threadlocal的引用鏈
最佳實踐
最佳實踐的方法參見google guava eventbus中對于ThreadLocal的使用
private final ThreadLocal<Boolean> dispatching;
this.dispatching = new ThreadLocal() {
protected Boolean initialValue() {
return Boolean.valueOf(false);
}
}
if(!((Boolean)this.dispatching.get()).booleanValue()) {
this.dispatching.set(Boolean.valueOf(true));
Dispatcher.PerThreadQueuedDispatcher.Event nextEvent;
try {
while((nextEvent = (Dispatcher.PerThreadQueuedDispatcher.Event)queueForThread.poll()) != null) {
while(nextEvent.subscribers.hasNext()) {
((Subscriber)nextEvent.subscribers.next()).dispatchEvent(nextEvent.event);
}
}
} finally {
this.dispatching.remove();
this.queue.remove();
}
}
- 采用匿名內(nèi)部類賦初始值
- 顯式調(diào)用get()、set()
- 在不用的時候顯式地remove()掉
- 對于顯示的remove特別重要,因為這樣可以避免entry不被GC的情況
如果為了避免ThreadLocal被GC,可以加強ThreadLocal的引用,將其聲明成private static
致謝
本文參考了解密ThreadLocal,引用了文中的圖片,感謝作者,侵刪。