接下來個人的學習方向偏向於 Android & Java 面試相關知識點系統性的總結,歡迎關注。
ThreadLocal
類是java.lang
包下的一個類,用於執行緒內部的資料儲存,通過它可以在指定的執行緒中儲存資料,本文針對該類進行原理分析。
通過思維導圖對其進行簡單的總結:
一.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
結構的容器,內部儲存著該執行緒作用域下的的所有變數副本,我們從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
中並返回。
這個初始化的方法的執行,需要開發者自己重寫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
// 否則,建立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
本身內部的邏輯都是圍繞著ThreadLocalMap
在運作,其本身更像是一個空殼,僅作為API
供開發者呼叫,內部邏輯都委託給了ThreadLocalMap
。
接下來我們來探究一下ThreadLocalMap
和Thread
以及ThreadLocal
之間的關係。
二、ThreadLocalMap分析
ThreadLocalMap
內部程式碼和演算法相對複雜,個人亦是一知半解,因此就不逐行程式碼進行分析,僅系統性進行概述。
首先來看一下ThreadLocalMap
的定義:
public class ThreadLocal<T> {
// ThreadLocalMap是ThreadLocal的內部類
static class ThreadLocalMap {
// Entry類,內部key對應的是ThreadLocal的弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
// 變數的副本,強引用
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
複製程式碼
ThreadLocal
中的巢狀內部類ThreadLocalMap
本質上是一個map
,依然是key-value
的形式,其中有一個內部類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
成員中加入資料;get()
同理。
三、記憶體洩漏的風險?
在上一小節中,我們看到ThreadLocalMap
中的Entry
中,其ThreadLocal
作為key
,是作為弱引用進行儲存的。
當ThreadLocal
不再被作為強引用持有時,會被GC回收,這時ThreadLocalMap
對應的ThreadLocal
就變成了null
。而根據文件所敘述的,當key == null
時,這時就可以預設該鍵不再被引用,該Entry
就可以被直接清除,該清除行為會在Entry
本身的set()/get()/remove()
中被呼叫,這樣就能 一定情況下避免記憶體洩漏。
這時就有一個問題出現了,作為key
的ThreadLocal
變成了null
,那麼作為value
的變數可是強引用呀,這不就導致記憶體洩漏了嗎?
其實一般情況下也不會,因為即使再不濟,執行緒在執行結束時,自然也會消除其對value
的引用,使得Value
能夠被GC回收。
當然,在某種情況下(比如使用了 執行緒池),執行緒再次被使用,Value
這時依然可以被獲取到,自然也就發生了記憶體洩漏,因此此時,我們還是需要通過手動將value
的值設定為null
(即呼叫ThreadLocal.remove()
方法)以規避記憶體洩漏的風險。
參考&感謝
- 《Android開發藝術探索》
- 深入理解ThreadLocal的"記憶體溢位"
- 關於ThreadLocal記憶體洩露的備忘
- ThreadLocal原始碼分析
關於我
Hello,我是卻把清梅嗅,如果您覺得文章對您有價值,歡迎 ❤️,也歡迎關注我的部落格或者Github。
如果您覺得文章還差了那麼點東西,也請通過關注督促我寫出更好的文章——萬一哪天我進步了呢?