Java ThreadLocal 的使用與原始碼解析
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 = %s\n", getName(), threadLocal.get()); sum += i; // 寫回資料 threadLocal.set(sum); } out.printf("END %s's sum = %d\n\n", 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 執行都會被回收。