接下來筆者的文章方向偏向于 Android & Java 面試相關知識點系統(tǒng)性的總結,歡迎關注。
ThreadLocal類是java.lang包下的一個類,用于線程內(nèi)部的數(shù)據(jù)存儲,通過它可以在指定的線程中存儲數(shù)據(jù),本文針對該類進行原理分析。
通過思維導圖對其進行簡單的總結:
一.ThreadLocal源碼分析
ThreadLocal類最重要的幾個方法如下:
- get():T 獲取當前線程下存儲的變量副本
- set(T):void 存儲該線程下的某個變量副本
- remove():void 移除該線程下的某個變量副本
1.get()方法分析
ThreadLocal類比較簡單,其最重要的就是get()和set()方法,顧名思義,起作用就是取值和設置值:
// 獲取當前線程中的變量副本
public T get() {
// 獲取當前線程
Thread t = Thread.currentThread();
// 獲取線程中的ThreadLocalMap對象
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;
}
}
// 若沒有該變量副本,返回setInitialValue()
return setInitialValue();
}
這里先將ThreadLocalMap暫時理解為一個Map結構的容器,內(nèi)部存儲著該線程作用域下的的所有變量副本,我們從ThreadLocal類中取值的時候,實際上是從ThreadLocalMap中取值。
如果Map中沒有該變量的副本,會從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;
}
可以看到,setInitialValue()中也非常的簡單,依然是從當前線程中獲取到ThreadLocalMap,略微不同的是,setInitialValue()會對變量進行初始化,存入ThreadLocalMap中并返回。
這個初始化的方法的執(zhí)行,需要開發(fā)者自己重寫initialValue()方法,否則返回值依然為null。
public class ThreadLocal<T> {
// ...
protected T initialValue() {
return null;
}
}
2.set()方法分析
和setInitialValue()方法類似,set()方法也非常簡單:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// map不為空,直接將ThreadLocal對象作為key
// 變量本身的值為value,存入map
map.set(this, value);
else
// 否則,創(chuàng)建ThreadLocalMap
createMap(t, value);
}
可以看到,這個方法的作用就是將變量副本作為value存入Map,需要注意的是,key并非是我們下意識認為的Thread對象,而是ThreadLocal本身(Thread和Value本身是一對一的,我們更容易將其映射為key-value的關系)。
3.remove()方法分析
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
對于變量副本的移除,也是通過map進行處理的,和set()和get()相同,Entry的鍵值對中,ThreadLocal本身作為key,對變量副本進行檢索。
4.小結
可以看出,ThreadLocal本身內(nèi)部的邏輯都是圍繞著ThreadLocalMap在運作,其本身更像是一個空殼,僅作為API供開發(fā)者調用,內(nèi)部邏輯都委托給了ThreadLocalMap。
接下來我們來探究一下ThreadLocalMap和Thread以及ThreadLocal之間的關系。
二、ThreadLocalMap分析
ThreadLocalMap內(nèi)部代碼和算法相對復雜,個人亦是一知半解,因此就不逐行代碼進行分析,僅系統(tǒng)性進行概述。
首先來看一下ThreadLocalMap的定義:
public class ThreadLocal<T> {
// ThreadLocalMap是ThreadLocal的內(nèi)部類
static class ThreadLocalMap {
// Entry類,內(nèi)部key對應的是ThreadLocal的弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
// 變量的副本,強引用
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
ThreadLocal中的嵌套內(nèi)部類ThreadLocalMap本質上是一個map,依然是key-value的形式,其中有一個內(nèi)部類Entry,其中key可以看做是ThreadLocal實例的弱引用。
和最初的設想不同的是,ThreadLocalMap中key并非是線程的實例Thread,而是ThreadLocal,那么ThreadLocalMap是如何保證同一個Thread中,ThreadLocal的指定變量唯一呢?
// 1.ThreadLocal的set()方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// ...
}
// 2.getMap()實際上是從Thread中獲取threadLocals成員
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public class Thread implements Runnable {
// 3.每個Thread實例都持有一個ThreadLocalMap的屬性
ThreadLocal.ThreadLocalMap threadLocals = null;
}
Thread本身持有ThreadLocal.ThreadLocalMap的屬性,每個線程在向ThreadLocal里setValue的時候,其實都是向自己的ThreadLocalMap成員中加入數(shù)據(jù);get()同理。
三、內(nèi)存泄漏的風險?
在上一小節(jié)中,我們看到ThreadLocalMap中的Entry中,其ThreadLocal作為key,是作為弱引用進行存儲的。
當ThreadLocal不再被作為強引用持有時,會被GC回收,這時ThreadLocalMap對應的ThreadLocal就變成了null。而根據(jù)文檔所敘述的,當key == null時,這時就可以默認該鍵不再被引用,該Entry就可以被直接清除,該清除行為會在Entry本身的set()/get()/remove()中被調用,這樣就能 一定情況下避免內(nèi)存泄漏。
這時就有一個問題出現(xiàn)了,作為key的ThreadLocal變成了null,那么作為value的變量可是強引用呀,這不就導致內(nèi)存泄漏了嗎?
其實一般情況下也不會,因為即使再不濟,線程在執(zhí)行結束時,自然也會消除其對value的引用,使得Value能夠被GC回收。
當然,在某種情況下(比如使用了 線程池),線程再次被使用,Value這時依然可以被獲取到,自然也就發(fā)生了內(nèi)存泄漏,因此此時,我們還是需要通過手動將value的值設置為null(即調用ThreadLocal.remove()方法)以規(guī)避內(nèi)存泄漏的風險。
參考&感謝
- 《Android開發(fā)藝術探索》
- 深入理解ThreadLocal的"內(nèi)存溢出"
- 關于ThreadLocal內(nèi)存泄露的備忘
- ThreadLocal源碼分析
關于我
Hello,我是卻把清梅嗅,如果您覺得文章對您有價值,歡迎 ??,也歡迎關注我的博客或者Github。
如果您覺得文章還差了那么點東西,也請通過關注督促我寫出更好的文章——萬一哪天我進步了呢?