為什么 ThreadLocal 可以做到線程隔離?
?對于 ThreadLocal 我們都不陌生,它的作用如同它的名字——用于存放「線程本地」變量。
先通過一個小例子感受一下:
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws Throwable {
Thread threadOne = new Thread(()->{
threadLocal.set("ThreadOne:" + Thread.currentThread().getName());
log.info("線程 One 本地變量值為:{}", threadLocal.get());
threadLocal.remove();
log.info("線程 One remove 后本地變量值為:{}", threadLocal.get());
});
Thread threadTwo = new Thread(()->{
threadLocal.set("ThreadTwo:" + Thread.currentThread().getName());
log.info("線程 Two 本地變量值為:{}", threadLocal.get());
});
threadOne.start();
threadTwo.start();
}
運行結果:
線程 One 本地變量值為:ThreadOne:Thread-0
線程 One remove 后本地變量值為:null
線程 Two 本地變量值為:ThreadTwo:Thread-1
OK,從效果上看,ThreadLocal 確實是線程隔離的,那么,它是如何做到線程隔離的呢?下面我們扒一扒源碼,看看它是如何做到的:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set() 方法的邏輯如下:
- 獲取當前線程
- 根據當前線程獲取一個 ThreadLocalMap 對象
- 如果 map 不為 null 則保存
- 如果 map 為 null 則創建一個 map
getMap() 和 createMap() 方法都干了啥呢?我們點進去看:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
進入到兩個方法內部后發現,不管執行哪個分支,最終是把值保存到了當前線程的 threadLocals 屬性中。
查看 Thread 類的源碼,你會發現類中定義了一個 threadLocals 屬性,且初始值為 null,其類型為ThreadLocal.ThreadLocalMap。
public class Thread implements Runnable {
// ...
ThreadLocal.ThreadLocalMap threadLocals = null;
// ...
}
到此,我們發現了,原來 ThreadLocal 就是把我們要傳遞的對象放到了當前線程的 threadLocals 屬性中。也就是說每個線程在用 ThreadLocal 保存對象時,其實就是將對象放到了當前線程實例對象的 threadLocals 屬性里面。這樣一來線程之間自然就是互相獨立的啦。
再看看 get() 方法:
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();
}
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;
}
ThreadLocal 的 get() 方法其實和 set() 方法邏輯很相似,先從當前線程的 threadLocals 屬性中取,如果該屬性為 null,那么就初始化。
當線程結束時,會調用當前線程實例的 exit() 方法,將 threadLocals 設置為 null,以便垃圾回收器將其回收掉。
// THread 類中的方法
private void exit() {
// ...
threadLocals = null;
// ...
}
最后,有一點需要格外注意:用完 ThreadLocal 一定要記得手動調用 remove() 方法,否則可能會產生臟數據甚至產生內存泄漏。
為啥呢?上面不是說線程結束時,會將 threadLocals 置為 null 嗎?
是的,線程結束時,確實會做清理工作。
但,如果線程一直不結束呢?如果線程會被復用呢?比如使用了線程池。
所以,使用 ThreadLocal 一定要手動 remove()。?