接下來我的的學習方向偏向於 Android & Java 面試相關知識點系統性的總結,歡迎關注。java
ThreadLocal
類是java.lang
包下的一個類,用於線程內部的數據存儲,經過它能夠在指定的線程中存儲數據,本文針對該類進行原理分析。android
經過思惟導圖對其進行簡單的總結:git
ThreadLocal
類最重要的幾個方法以下:github
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()
中取值:app
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;
}
}
複製代碼
和setInitialValue()
方法相似,set()
方法也很是簡單:this
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
的關係)。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
複製代碼
對於變量副本的移除,也是經過map
進行處理的,和set()
和get()
相同,Entry
的鍵值對中,ThreadLocal
自己做爲key
,對變量副本進行檢索。
能夠看出,ThreadLocal
自己內部的邏輯都是圍繞着ThreadLocalMap
在運做,其自己更像是一個空殼,僅做爲API
供開發者調用,內部邏輯都委託給了ThreadLocalMap
。
接下來咱們來探究一下ThreadLocalMap
和Thread
以及ThreadLocal
之間的關係。
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()
方法)以規避內存泄漏的風險。
Hello,我是卻把清梅嗅,若是您以爲文章對您有價值,歡迎 ❤️,也歡迎關注個人博客或者Github。
若是您以爲文章還差了那麼點東西,也請經過關注督促我寫出更好的文章——萬一哪天我進步了呢?