帶你揭祕神祕的ThreadLocal
說起ThreadLocal 大家應該有種很熟悉的感覺,但是又好像不知道是幹啥用的,第一次接觸它還是在Looper的原始碼中,每次獲取Looper物件是,通過ThreadLocal的get方法獲取到當前執行緒的Looper物件,有興趣的可以看看之前的文章Android原始碼學習之handler ,為什麼要通過ThreadLocal來獲取Looper物件呢,亦或者說這樣做有什麼好處?今天就帶大家一起深入瞭解這個神祕的ThreadLocal。
原始碼
話不多說,直接開擼:
/** * This class provides thread-local variables.These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable.{@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). */ public class ThreadLocal<T> { private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } public ThreadLocal() { } 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(); } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } } 複製程式碼
從類上面的註釋可以看到,大概翻譯下也就是:該類提供執行緒區域性變數,這些變數與正常的變數不同,而是每個訪問一個的執行緒都有自己獨立初始化的變數副本,ThreadLocal例項通常是類中的私有靜態欄位,希望將狀態與執行緒關聯
不要羨慕鄙人的英語,因為。。我是google翻譯的...(咳咳)
這裡只是摘了一段程式碼,從上面暴露的方法可以看到,提供了set,get方法,很明顯就能看出來,set方法時,key是this,也就是當前的ThreadLocal物件,value就是傳遞進來的值,而最終是儲存到哪呢,一個叫ThreadLocalMap的物件,追蹤一下,發現它其實是ThreadLocal的靜態內部類:
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } 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(); } } } 複製程式碼
Set方法
可以看到內部再維護了一個靜態Entry類繼承弱引用,所以上面所說的key,ThreadLocal物件其實是咦弱引用的形式儲存的,這樣也有益於GC回收,防止記憶體洩漏,我們先來看set方法:
- 通過key的雜湊碼和陣列長度,計算出儲存元素的下標,這點應該很類似於HashMap中的找陣列下標的方式。
- 找到下標之後,一個迴圈,從i開始往後一直遍歷到陣列最後一個Entry,如果key相等,覆蓋value,如果key為null,用新key、value覆蓋,同時清理歷史key=null的陳舊資料
- 如果找到下標為空的元素,跳出迴圈,將key和value,設定進去,填滿該下標元素位置,同時size++,如果超過閾值,重新hash
private void rehash() { //清理一次舊的資料 expungeStaleEntries(); //如果當前size大於3/4的閾值,就進行擴容 if (size >= threshold - threshold / 4) resize(); } private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; //將長度擴容到之前的2倍 int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; if (e != null) { ThreadLocal<?> k = e.get(); //取出ThreadLocal物件 if (k == null) { e.value = null; // Help the GC } else { //如果不為空,類似上面的迴圈,一直找到一個沒有使用的位置,在空節點上塞入Entry int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; } 複製程式碼
大部分註釋,其實都是根據裡面的英文註釋翻譯過來的,所以想了解的可以靜下心來好好的翻一翻原始碼,相信我,你會有意外的收穫。
Get方法
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //通過當前執行緒,獲取ThreadLocalMap,如果不為空,返回value,否則走初始化流程 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return 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; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //初始化,設定閾值位int值16 table = new Entry[INITIAL_CAPACITY]; //計算陣列下標 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } //閾值設定為容量的*2/3,即負載因子為2/3,超過就進行再雜湊 private void setThreshold(int len) { threshold = len * 2 / 3; } 複製程式碼
- 從當前執行緒中獲取ThreadLocalMap,查詢當前ThreadLocal變數例項對應的Entry,如果不為null,獲取value,否則進入初始化
- 初始化,設定陣列初始長度,閾值等等引數