ThreadLocal是JDK中的一個類,不少基礎框架和平時開發中都會使用到,所以有必要弄清其內部原理,才能更好地使用它。java
要弄清原理,仍是要先知道如何使用,ThreadLocal用起來是很簡單的,通常都是把ThreadLocal定義爲static變量,也就是隻有一個實例對象,以下:git
private static ThreadLocal<Integer> sThreadLocal = new ThreadLocal<Integer>();
複製代碼
而後就能夠在各個線程中使用這個sThreadLocal了:github
for (int i = 0; i < 5; i++) {
final int index = i;
new Thread() {
public void run() {
sThreadLocal.set(index);
try {
Thread.sleep(1000);
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println(sThreadLocal.get());
}
}.start();
}
複製代碼
能夠看到,一個ThreadLocal只能用來保存一個對象,若是須要保存多個對象,就須要定義多個ThreadLocal。數組
ThreadLocal的做用就是用來保存線程相關的局部對象,當某個對象跟線程有一對一的關係,就能夠使用ThreadLocal進行保存。併發
在Android中,咱們知道一個線程對應一個Looper,所以Looper內部就使用到了ThreadLocal,把線程的Looper對象保存在ThreadLocal中。又好比AnimationHandler類,它內部也定義了一個ThreadLocal對象,用於保存線程的AnimationHandler對象,也就是說,線程的動畫執行最終都是在被同一個AnimationHandler對象處理的,有興趣能夠本身看源碼。框架
咱們還注意到,定義的ThreadLocal雖然在多個線程中被使用,但不會有線程問題,由於每一個線程訪問的都是本身的局部對象。oop
總體原理圖性能
這個Thread就是線程對象,每一個線程只有一個,能夠經過Thread.currentThread()方法拿到,也能夠在new Thread()時拿到,無論什麼方式拿到的都是同個對象,這個對象裏面有一個哈希表,每個Entry的key就是ThreadLocal的弱引用,而value就是set進來的局部對象。優化
能夠看到這個哈希表是線程惟一的,而裏面的每個Entry,對應一個ThreadLocal對象,好比說我在線程A中用到了3個ThreadLocal,都設置了不一樣的局部對象,那麼這個哈希表就有3個Entry對象。動畫
ThreadLocalMap
上面所說的線程Thread對象中的哈希表,其真實類型就是ThreadLocalMap,它實際上是一個簡化版哈希表,初始容量爲16,固然,只有真的放東西Entry數組纔會初始化,threshold爲容量的2/3,超過了就觸發擴容,擴容也是比較簡單的,就是直接擴大爲原容量的2倍,至於哈希衝突問題,若是衝突就採用線性探測的方法解決。
這裏還有個問題,就是如何根據key肯定放到哪一個桶裏,能夠看下代碼:
int i = key.threadLocalHashCode & (len-1);
複製代碼
threadLocalHashCode在初始化時被賦值:
public class ThreadLocal<T> {
......
private final int threadLocalHashCode = nextHashCode();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
......
}
複製代碼
也就是說,這個threadLocalHashCode,就是0x61c88647的整數倍,而後跟len-1進行與操做,就獲得了桶的下標,至於爲何是0x61c88647這個數值,網上有分析文章,有興趣能夠本身查看。
內存泄漏
根據上面的總體原理圖,能夠獲得一條引用鏈:
Thread對象 --> threadLocals --> Entry[] --> WeakReference<ThreadLocal<?>>和value
複製代碼
由於Thread對象的生命週期是比較長的,只有當線程退出後,Thread對象纔會被回收,那麼在線程退出前,咱們的ThreadLocal被WeakReference包着,若是外部沒有強引用,在內存不足時gc會自動回收了,但那個value就不會了,須要咱們本身手動去清空引用。
所以這裏存在一個內存泄漏的問題,若是某個局部對象使用ThreadLocal保存了,而後用完以後沒有清除掉,線程又還沒退出,就可能致使內存泄漏,固然解決的方法也很簡單,調用remove方法就能夠:
sThreadLocal.remove();
複製代碼
另外,並非說這種場景下咱們不調用remove方法,value引用就一直不會被置空,ThreadLocal內部作了優化,在get和set時會主動置空那些key被gc自動回收的value引用,不過通常咱們的ThreadLocal都是定義爲static,這種狀況就不可能會被gc自動回收了。
Netty的FastThreadLocal
若是同個線程用到了多個ThreadLocal,也就是Entry[]會有多個元素,那麼每次在搜索某個ThreadLocal對應在哪一個桶這個過程就會比較耗時,特別是對於服務端併發量很大的狀況下,性能損耗會很明顯,所以FastThreadLocal就作了優化,直接把ThreadLocal的哈希表去掉了,改成用變量index記錄當前ThreadLocal對應的數組下標,也就是空間換時間,實現代碼:FastThreadLocal.java,固然,在Android中對同個線程的ThreadLocal哈希表頻繁操做這種場景可能並很少見。