ThreadLocal
背景
在android的消息隊列中钥顽,每個線程都可以創(chuàng)建一個Looper题画,那么在android中是如何管理Looper和線程的關(guān)系呢钦幔?在Looper.prepare()方法中可以看到窃诉,這里通過了一個ThreadLocal進(jìn)行每個線程中Looper的管理豆胸。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
ThreadLocal是怎樣工作的奥洼,是怎樣保證不同線程中變量存取正確的,下面從源碼的角度看一看晚胡。
ThreadLocal
下面從源碼角度來看看ThreadLocal實現(xiàn)了什么樣的功能灵奖,從set()方法看起嚼沿,在set()方法中首先獲取了當(dāng)前調(diào)用set()方法的線程,然后通過這個線程獲取了一個ThreadLocalMap對象瓷患,如果存在就把當(dāng)前的ThreadLocal和需要儲存的value放到這個ThreadLocalMap對象中骡尽,如果不存在就新建一個ThreadLocalMap把當(dāng)前thread和value存進(jìn)去。代碼如下
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
這里看到creatMap()方法并沒有以當(dāng)前的ThreadLocal為key進(jìn)行存儲擅编,看看createMap()就不奇怪了攀细,在createMap()方法中還是通過當(dāng)前的ThreadLocal進(jìn)行存儲的,代碼如下
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
有set()就會有g(shù)et()爱态,下面來看看get()方法谭贪。首先還是先通過當(dāng)前線程thread對象獲取ThreadLocalMap,如果map存在再通過當(dāng)前ThreadLocal作為key進(jìn)行查找锦担,如果存在的話就返回對應(yīng)的值俭识,如果不存在則進(jìn)行一次對value的初始化,代碼如下
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();
}
可以看到調(diào)用get()方法后洞渔,如果當(dāng)前ThreadLocal對應(yīng)的value不存在套媚,就會進(jìn)行一次初始化,在setInitialValue()中第一步就對value進(jìn)行了初始化磁椒,初始化的同時堤瘤,還是根據(jù)當(dāng)前線程去生成了一個ThreadLocalMap,并進(jìn)行和set()方法一樣的初始化浆熔,然后返回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;
}
在初始化value時調(diào)用了initialValue(),這里直接暴力的返回了null蘸拔,但是可以看到這個方法是protected的师郑,應(yīng)該是方便繼承時自己定義初始化值使用的环葵,如果直接重寫setInitialValue()是非常不安全的调窍。initialValue()代碼如下
protected T initialValue() {
return null;
}
上述方法還會調(diào)到getMap()方法,這個方法也很簡單张遭,直接返回和當(dāng)前thread綁定的threadLocals邓萨,代碼如下。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
以上就是ThreadLocal存取數(shù)據(jù)的主要代碼菊卷,上面一直提到ThreadLocalMap缔恳,下面來看看它究竟是什么。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的一個靜態(tài)內(nèi)部類洁闰,從命名上猜測歉甚,它的作用應(yīng)該是類似于Java中的Map。首先在它的內(nèi)部定義了一個Entry類扑眉,定義了一個key-value格式的映射纸泄,key是ThreadLocal赖钞,value是任意類型,代碼如下
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
下面看看ThreadLocalMap中default的構(gòu)造方法聘裁,這個構(gòu)造方法只有在ThreadLocalMap第一次創(chuàng)建的時候調(diào)用雪营,即調(diào)用createMap()方法時調(diào)用,可以看到在構(gòu)造方法內(nèi)對Entry進(jìn)行了初始化衡便,其中的table是一個Entry數(shù)組献起,并傳出一個默認(rèn)為16的長度,通過ThreadLocal計算出一個hash值镣陕,并存在數(shù)組中下表的位置谴餐,這里和HashMap的實現(xiàn)原理類似,代碼如下茁彭。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
在ThreadLocal中獲取entry時類似HashMap的取值方法总寒,通過Hash值計算出下標(biāo),從數(shù)組中取出對應(yīng)entry理肺,代碼如下
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);
}
set()的時候也很簡單摄闸,如果key存在則替換value,不存在則通過replaceStaleEntry()去新增一個妹萨,代碼如下
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
小結(jié)
以上就是ThreadLocal中主要的幾個方法年枕,通過ThreadLocal和value進(jìn)行映射,對數(shù)據(jù)進(jìn)行存儲乎完,用起來十分簡單和方便熏兄,在處理多線程中變量儲存的時候可以起到很好的效果。