ThreadLocal
開胃菜
?研究過Handler的應(yīng)該對ThreadLocal比較眼熟的,線程中的Handler對象就是通過ThreadLocal來存放的。初識ThreadLocal的可能被它的名字有所誤導(dǎo),ThreadLocal初一看可能會覺得這是某種線程實現(xiàn),而實際并非如此。事實上,它是一個全局變量,用來存儲對應(yīng)Thread的本地變量,這也是為什么將其稱之為Local。當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本。
?比如Handler,當我們在一個線程中創(chuàng)建了一個Handler時,在調(diào)用Looper.prepare()時通過ThreadLocal保存了當前線程下的Looper對象,而所有線程的Looper都由一個ThreadLocal來維護,也就是在所有線程中創(chuàng)建的Looper都存放在了一個ThreadLocal中,然后創(chuàng)建Handler將Handler與當前線程Looper關(guān)聯(lián),當調(diào)用Looper.loop()的時候通過myLooper()得到的就是當前線程的Looper,當在其他線程使用Handler來發(fā)送消息的時候,其實也就是將對應(yīng)的Message存儲到了對應(yīng)Handler的MessageQueue中,當Looper去分發(fā)消息的時候,就是將當前線程中的Looper對應(yīng)的MessageQueue中的Message通過Handler的回調(diào)返回給了Handler所在的線程。如對Handler不了解的,可參考Handler全面解讀

ThreadLocal模擬
?那么,如何來做到區(qū)分不同線程中的變量呢?我們這里模擬一個實現(xiàn)ThreadLocal功能的類,原理大致一樣。
public class ThrealLocalImitation<T> {
private static Map<Thread, Object> sSaveValues = new HashMap<Thread, Object>();
public synchronized void set(T threadData) {
Thread thread = Thread.currentThread();
mSaveValues.put(thread, threadData);
}
public synchronized T get() {
Thread thread = Thread.currentThread();
return (T) mSaveValues.get(thread);
}
}
?如上ThreadLocal模仿類里面,通過全局的Map變量sSaveValues,以Thread為key,value為對應(yīng)線程需要保存的變量,實現(xiàn)了,每個線程對應(yīng)保存了一個變量,在不同的線程存儲不同的變量,通過get方法就能取回對應(yīng)的值。
ThreadLocal原理分析
?ThreadLocal類通過set、get方法來分別存取變量的,搞懂了這兩個方法的功能也就明白ThreadLocal的原理了,所以重點分析這兩個方法。
在進入正式的分析之前先來看一個類——ThreadLocalMap
?和我們模擬的ThreadLocal稍有所區(qū)別,ThreadLocal不是直接通過一個Map來存儲Thread和value對應(yīng)關(guān)系的。
?在Thread類中,有一個變量ThreadLocal.ThreadLocalMap。
?先看下該類的其中一個構(gòu)造方法
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);
}
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
?ThreadLocalMap內(nèi)部維護一個數(shù)組Entry[] table, Entry對應(yīng)存儲了key--value(ThreadLocal---value)。ThreadLocalMap實際上是一個實現(xiàn)了自定義的尋址方式的HashMap。
?那么ThreadLocal是如何存儲線程本地變量的呢?先給個簡單的結(jié)論。
每個Thread在生命周期中都會維護著一個ThreadLocalMap,可以看成是一個存儲了ThreadLocal(key)---value的HashMap,當ThreadLocal存儲value時,先通過當前Thread得到其維護的ThreadLocalMap,然后將其存儲到該map中,而獲取value時則是先獲取到當前線程的ThreadLocalMap,然后通過當前的ThreadLocal,獲取到ThreadLocalMap存儲的value值。
set()方法
public void set(T value) {
Thread t = Thread.currentThread();//獲取到當前線程
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
?set方法中,首先通過Thread.currentThread()獲取到當前的線程,通過當前線程獲得其維護的ThreadLocalMap,當map為空時,則為當前Thread創(chuàng)建一個ThreadLocalMap,不為空的話則將ThreadLocal--value存儲到map中。
所以一個Thread對應(yīng)著一個ThreadLocalMap,而一個ThreadLocalMap對應(yīng)著多個ThreadLocal。
get()方法
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();
}
?get()方法,同樣是先獲取到當前Thread,然后獲取到當前Thread的ThreadLocalMap,然后根據(jù)ThreadLocal自身,通過ThreadLocalMap自身的尋址方式獲取到存儲ThreadLocal和value的Entry對象,進而得到value。
?setInitialValue();是當ThreadLocalMap為空時,可以通過實現(xiàn)ThreadLocal的initialValue()來獲得一個默認值,同時該默認值會被存儲到線程的ThreadLocalMap中。
內(nèi)存泄漏
?分析到這里,ThreadLocal的原理已經(jīng)很明朗了。但是一些使用不當?shù)那闆r出現(xiàn)內(nèi)存泄漏的風險,所以最后講解下ThreadLocal會出現(xiàn)的內(nèi)存泄漏風險,及如何避免。
?ThreadLocalMap中存儲的Entry為ThreadLocal--value,準確的描述應(yīng)該是weakReference(ThreadLocal)--value,即,key(ThreadLocal為弱引用),而value則是強引用的,當ThreadLocal為空后,Thread不會再持有ThreadLocal引用,ThreadLocal可以被GC回收,但是Thread的ThreadLocalMap仍然還持有value的強引用,導(dǎo)致value需要等待線程生命周期結(jié)束才可能被GC回收。當出現(xiàn)一些長時間存在的線程,不斷的存儲了內(nèi)存比較大的value,而value實際是不再被使用的,value由于線程沒有被回收而不斷的堆積,造成了內(nèi)存泄漏。比如當使用到線程池是,Thread很有可能不會被馬上結(jié)束,可能會被不斷的重復(fù)利用。
?所以這里引入ThreadLocal的另外一個方法——remove方法
remove()方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
//ThreadLocalMap中的remove
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
?所以在value不再使用時,應(yīng)該及時調(diào)用remove,解除線程對該value的引用。