ThreadLocal原理詳細解析
1.ThreadLocal概念
ThreadLocal,可以叫做執行緒本地變數或執行緒本地儲存,顧名思義就是ThreadLocal為變數在每個執行緒中都建立了一個副本,那麼每個執行緒可以訪問自己內部的副本變數。其實就是通過空間換時間的方式來取得對每個執行緒各自變數的共享。
變數值的共享可以使用 public static 變數的形式,所有的執行緒都使用同一個被 public static 修飾的變數。ThreadLocal主要解決的就是每個執行緒繫結自己的值,可以將ThreadLocal類比喻成全 局存放資料的盒子,盒子中可以儲存每個執行緒的私有變數。
2.具體原始碼分析
-
ThreadLocal有以下方法:
- 方法的具體實現:
- 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(); }
1.獲取當前執行緒t;2.通過getMap方法獲取一個ThreadLocalMap型別的map;3.獲取這個鍵值對,注意這裡獲取鍵值對傳進去的是 this,而不是當前執行緒t。
如果獲取成功,則返回value值。如果map為空,則呼叫setInitialValue方法返回value。
-
getMap方法:
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } //Thread類 ThreadLocal.ThreadLocalMap threadLocals = null;
根據傳入的當前執行緒t返回它的區域性變數threadLocals,而這個threadLocals實際上是ThreadLocalMap型別的,而ThreadLocalMap又是ThreadLocal的一個內部類。
-
ThreadLocalMap:
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
ThreadLocalMap中的Entry繼承了WeakReference,並且使用ThreadLocal型別的值作為key。
-
接下來看get方法中返回的setInitialValue方法:
/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ 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; }
當map不為空時,將鍵值對存進去,當map為空時,重新建立一個map
-
createMap方法:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
-
- get方法:
到這裡基本就可以看出ThreadLocal是如何為每個執行緒建立變數的副本的:
首先,在每個執行緒Thread內部有一個ThreadLocal.ThreadLocalMap型別的成員變數threadLocals,這個threadLocals就是用來儲存實際的變數副本的,key為當前ThreadLocal變數,value為變數副本(即T型別的變數)。
初始時,在Thread裡面,threadLocals為空,當通過ThreadLocal變數呼叫get()方法或者set()方法,就會對Thread類中的threadLocals進行初始化,並且以當前ThreadLocal變數為鍵值,以ThreadLocal要儲存的副本變數為value,存到threadLocals。
然後在當前執行緒裡面,如果要使用副本變數,就可以通過get方法在threadLocals裡面查詢。
- 例子:
public class Test {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final Test test = new Test();
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread(){
public void run() {
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
這段程式碼的輸出結果為:
從執行結果看出:因為例項化了兩個ThreadLocal變數,所以他們對各自的區域性變數儲存的副本值是不一樣的,因此在兩個執行緒中的執行並不會互相影響各自的變數值。
總結一下:
1)實際的通過ThreadLocal建立的副本是儲存在每個執行緒自己的threadLocals中的;
2)為何threadLocals的型別ThreadLocalMap的鍵值為ThreadLocal物件,因為每個執行緒中可有多個threadLocal變數,就像上面程式碼中的longLocal和stringLocal;
3)在進行get之前,必須先set,否則會報空指標異常;
如果想在get之前不需要呼叫set就能正常訪問的話,必須重寫initialValue()方法,設定預設值。
因為在上面的程式碼分析過程中,我們發現如果沒有先set的話,即在map中查詢不到對應的儲存,則會通過呼叫setInitialValue方法返回i,而在setInitialValue方法中,有一個語句是T value = initialValue(), 而預設情況下,initialValue方法返回的是null。
3.值繼承
使用InheritableThreadLocal類可以實現值的繼承,讓子執行緒從父執行緒中取得值。不過要注意,如果子執行緒在取得值的同時,父執行緒將InheritableThreadLocal中的值進行修改,那麼子執行緒取到的還是原來的值。