一、ThreadLocal
ThreadLocal是本地線程變量,其中填充的的是當前線程的變量,該變量對其他線程而言是封閉且隔離的,ThreadLocal為變量在每個線程中創建了一個副本,這樣每個線程都可以訪問自己內部的副本變量。
它與普通變量的區別在于,每個使用該變量的線程都會初始化一個完全獨立的實例副本,通過統一的操作方式,實現了不同線程使用不通的變量,使用起來比較方便。
Thread_1 --------> var_1
Thread_2 --------> var_2
Thread_3 --------> var_3
...
二、ThreadLocal怎么用
public class Main {
public static void main(String[] args) {
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
IntStream.range(0, 10).forEach(i -> {
new Thread(() -> {
threadLocal.set(i);
// 業務邏輯...
System.out.println(Thread.currentThread().getName() + "threadLocal var: " + threadLocal.get());
}).start();
});
}
}
/**
* Thread-0threadLocal var: 0
* Thread-4threadLocal var: 4
* Thread-3threadLocal var: 3
* Thread-2threadLocal var: 2
* Thread-1threadLocal var: 1
*/
可以看到在不同的線程中通過訪問同一變量threadLocal,而獲取到的是不同的值。
在一個應用系統中,一般是通過創建一個線程去負責處理業務邏輯,而業務邏輯的處理過程,通常需要跨越多個方法,多個方法之間可能需要相同的參數,我們稱為context上下文信息,如果方法之間來回傳遞這個context是比較麻煩的,而ThreadLocal很好地解決了這個問題。
從實際應用的角度,ThreadLocal可以:
- 在進行對象跨層傳遞的時候,使用
ThreadLocal可以避免多次傳遞,打破層次間的約束。 - 線程間數據隔離
- 進行事務操作,用于存儲線程事務信息。
- 數據庫連接,
Session會話管理。
三、ThreadLocal的實現原理
Thread類中有兩個變量:
- threadLocals
- inheritableThreadLocals
二者的類型都是ThreadLocal的內部類ThreadLocalMap類型,其類似HashMap,默認情況下二者都是null。當線程第一次調用ThreadLocal的get或set方法時會創建它們。
/---------------------------\ /-----------------------\
| | | |
| Thread | | ThreadLocalMap |
| | | |
|1. threadLocals | | Entry |
|2. inheritableThreadLocals | | key | value |
| | | tl | var |
| | | |
\---------------------------/ \-----------------------/
ThreadLocal的set()方法:
public void set(T value) {
// 獲取當前線程,稱為調用者線程
Thread t = Thread.currentThread();
// 以調用者線程作為key值,去查找對應的線程變量,找到對應的map
ThreadLocalMap map = getMap(t);
// 如果map不為null,就直接添加本地變量,key為當前定義的ThreadLocal變量的this引用,值為添加的本地變量值
if (map != null)
map.set(this, value);
// 如果map為null,說明首次添加,需要首先創建出對應的map
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
// 獲取線程自己的變量threadLocals,并綁定到當前調用線程的成員變量threadLocals上
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
// createMap方法不僅創建了threadLocals,同時也將要添加的本地變量值添加到了threadLocals中。
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get()方法:
public T get() {
// 獲取調用者線程
Thread t = Thread.currentThread();
// 獲取當前線程的threadLocals變量
ThreadLocalMap map = getMap(t);
// 如果threadLocals變量不為null,就可以在map中查找到本地變量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 執行到此處,threadLocals為null,調用該更改初始化當前線程的threadLocals變量
return setInitialValue();
}
private T setInitialValue() {
//protected T initialValue() {return null;}
T value = initialValue();
// 獲取調用者線程
Thread t = Thread.currentThread();
// 以當前線程作為key值,去查找對應的線程變量,找到對應的map
ThreadLocalMap map = getMap(t);
// 如果map不為null,就直接添加本地變量,key為當前線程,值為添加的本地變量值
if (map != null)
map.set(this, value);
// 如果map為null,說明首次添加,需要首先創建出對應的map
else
createMap(t, value);
return value;
}
所以在不同的線程中通過get()方法獲取threadLocal變量,本質上是從當前thread的threaLocals存的變量。
四、避免內存泄漏
在第三節介紹了每個Thread都維護了一個ThreadLocalMap,key為ThreadLocal實例,為弱引用,value為線程變量的副本,他們的引用關系:
-------------------------------------Heap-----------------------\
/-----------Stack-----------\ | ------------\ |
| | | | | |
| | | v | |
| ThreadLocalRef --------------------|-----> ThreadLocal | /-------------------\ |
| | | | | ThreadLocalMap | |
| CurrentTreadRef --------------------|-----> CurrentThread ------|-----> | Entry | |
| | | | | key | value | |
| | | | | tl | var | |
\---------------------------/ | | | | | | |
| | \--------|------|---/ |
| \----------------/ | |
\---------------------------------------------------------------/
|
v
線程變量val
從它們的引用關系可以看出來,threadLocalMap中的key為ThreadLocal的弱引用,當ThreadLocal在其他地方不存在強引用時,key(ThreadLocal)勢必會被GC回收,這樣key變成了null。而value還存在著強引用不會被GC回收,只有當當前進程結束這個強引用才會斷開,但如果線程遲遲不結束,這個強引用就會一直存在,value就會一直存在內存中,造成內存泄漏。
因此如果threadlocal不再使用時,通過remove()方法刪除,避免內存泄漏的情況發生。