Java ThreadLocal 的使用與源碼解析
- 2019 年 10 月 22 日
- 筆記
GitHub Page: http://blog.cloudli.top/posts/Java-ThreadLocal-的使用與源碼解析/
ThreadLocal
主要解決的是每個線程綁定自己的值,可以將 ThreadLocal
看成全局存放數據的盒子,盒子中存儲每個線程的私有數據。
驗證線程變量的隔離性
import static java.lang.System.out; public class Run { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); static class Work extends Thread { @Override public void run() { threadLocal.set(0); for (int i = 1; i <= 5; i++) { // 獲取數據 int sum = threadLocal.get(); out.printf("%s's sum = %sn", getName(), threadLocal.get()); sum += i; // 寫回數據 threadLocal.set(sum); } out.printf("END %s's sum = %dnn", getName(), threadLocal.get()); } } public static void main(String[] args) { Work work1 = new Work(), work2 = new Work(); work1.start(); work2.start(); } }
運行結果:
Thread-0's sum = null Thread-1's sum = null Thread-1's sum = 1 Thread-1's sum = 3 Thread-1's sum = 6 Thread-1's sum = 10 END Thread-1's sum = 15 Thread-0's sum = 1 Thread-0's sum = 3 Thread-0's sum = 6 Thread-0's sum = 10 END Thread-0's sum = 15 Process finished with exit code 0
從結果來看,兩個線程的計算結果一致,ThreadLocal
中隔離了兩個線程的數據。
ThreadLocal 源碼解析
ThreadLocalMap 內部類
在 ThreadLocal
中有一個 ThreadLocalMap
內部類,所以 ThreadLocal
實際上是使用一個哈希表來存儲每個線程的數據的。
ThreadLocalMap
與 HashMap
不同,其中 Entry
是一個弱引用,這意味着每次垃圾回收運行時都會將儲存的數據回收掉。而且它只使用了數組來存儲鍵值對。
ThreadLocalMap
中的 Entry
:
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
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(); }
get()
方法首先得到當前線程,然後獲取當前線程的 ThreadLocalMap
對象,然後從中取出數據。
這裡的 map.getEntry(this)
看起來很奇怪,在前面有這樣一行代碼:
ThreadLocalMap map = getMap(t);
這個方法獲取當前線程的 ThreadLocalMap
對象,所以,雖然 map.getEntry()
中的 key
總是 ThreadLocal
對象本身,但是每個線程都持有有自己的 ThreadLocalMap
對象。
getMap() 方法
/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
看到這個方法,get()
方法中 map.getEntry(this)
的迷霧就解開了。這裡可以看到返回的是線程中的 threadLocals
屬性。那麼這裡瞟一眼 Thread
的源碼:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
其實每次 get()
時都是先獲取了線程自己的 ThreadLocalMap
對象,然後對這個對象進行操作。
set() 方法
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else // 為當前線程創建一個 ThreadLocalMap 對象 createMap(t, value); }
set()
方法也是先獲取當前線程自己的 ThreadLocalMap
對象,然後再設置數據。如果獲取的哈希表為 null,則創建一個。
createMap() 方法
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
createMap()
方法創建一個 ThreadLocalMap
對象,該對象由線程持有。
總結
ThreadLocal
可以隔離線程的變量,每個線程只能從這個對象中獲取到屬於自己的數據。ThreadLocal
使用哈希表來存儲線程的數據,而且這個哈希表是由線程自己持有的,每次獲取和設值都會先獲取當前線程持有的ThreadLocalMap
對象。ThreadLocalMap
中的key
總是ThreadLocal
對象本身。ThreadLocalMap
中的Entry
是弱引用,每次 GC 運行都會被回收。