此類提供線程局部變量。這些變量與普通變量不同,每個訪問一個線程(通過其get
或set
方法)的線程 都有其自己的,獨立初始化的變量副本。 ThreadLocal
實例通常是希望將狀態與線程關聯的類中的私有靜態字段(例如,用戶ID或事務ID)。
以射擊游戲舉例,游戲開始時,每個人能夠領到一把槍,槍把上有三個數字:子彈數、殺敵數、自己的命數,為其設置的初始值分別為100、0、10.設戰場上的每個人都是一個線程,那么這三個初始值寫在哪里呢?
如果每個線程都寫死這三個值,萬一將初始子彈數統一改成 1000發呢?
如果共享,那么線程之間的并發修改會導致數據不準確.
能不能構造這樣一個對象,將這個對象設置為共享變量,統一設置初始值,但是每個線程對這個值的修改都是互相獨立的.這個對象就是ThreadLocal
一、類定義
public class ThreadLocal<T> {}
…用來限制Class中的參數類型,確保Class中參數的一致性
二、實例變量和相關方法
//用于ThreadLocalMap
private final int threadLocalHashCode = nextHashCode();//下一個hash code,從0開始
private static AtomicInteger nextHashCode = new AtomicInteger();//hash增量
private static final int HASH_INCREMENT = 0x61c88647;//在獲取下一個hash code
private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);
}
三、內部類
內部類:ThreadLocalMap
ThreadLocalMap
負責存儲ThreadLocal
及其變量,即ThreadLocal
對象本身作為鍵,ThreadLocal
存儲的變量作為值。每個Thread
對象在聲明一個ThreadLocal
后會持有一個ThreadLocalMap
的引用,來實現ThreadLocal
的功能。
ThreadLocalMap
持有一個內部類Entry
,類似于HashMap.Node
類,負責保存鍵值對。
static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}
Entry
繼承了WeakReference
類,使Entry
的鍵為弱引用。
看到這里很多人或許有這樣一個疑問:為什么Entry要繼承WeakReference?
既然將ThreadLocal
聲明為弱引用,那么自然會聯想到和GC有關。
如果不聲明為弱引用,以最上面Test類的代碼為例,當我們將上述ThreadLocal
類型的靜態變量tl
設置為null
時,Thread
對象成員變量threadLocals
依然保留有一個ThreadLocalMap
,該Map中持有保存該ThreadLocal
的Entry
,在這個線程運行期間無法GC,從而引發內存泄漏。所以,Entry
的鍵要聲明為弱引用。
四、主要方法
1.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();
}
首先是獲取當前運行線程對象,然后根據該線程對象找到對應的ThreadLocalMap
。
- 如果找到了該線程對應的
ThreadLocalMap
,則通過當前ThreadLocal
對象作為鍵查找Map
中對應的Entry
(鍵值對)對象 - 如果查找結果不為
null
,則返回Entry
對象的value
。否則調用setInitialValue
方法將當前ThreadLocal
對象(this)和變量作為鍵值對存入ThreadLocalMap
并返回變量。
2.initialValue()
為變量設置初始值,該方法的默認實現是:
protected T initialValue() {return null;
}
如果想要為該變量設置一個初始值,只需重寫該方法即可,例如:
@Override
protected String initialValue() {return "hello world";
}
3.set(T value)
與get
方法類似,set
方法首先會獲取當前運行的Thread
對象并通過該對象獲取對應的ThreadLocalMap
,如果map為空,則為當前Thread
對象新建一個ThreadLocalMap
,否則直接將value
放入map
中。
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}
4、remove()
同樣,獲取當前Thread
對應的ThreadLocalMap
,然后調用ThreadLocalMap
的remove
方法移除ThreadLocal
對象,無需通過弱引用機制對該ThreadLocal
對象進行GC。
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}
五、總結
ThreadLocal是如何做到為每一個線程維護變量的副本的呢?
在ThreadLocal
類中設置了一個Map,存儲每一個線程的變量的副本。
ThreadLocal
使用場合主要解決多線程中數據數據因并發產生不一致問題。ThreadLocal為每個線程的中并發訪問的數據提供一個副本,通過訪問副本來運行業務,這樣的結果是耗費了內存,單大大減少了線程同步所帶來性能消耗,也減少了線程并發控制的復雜度。
Synchronized用于線程間的數據共享,而ThreadLocal則用于線程間的數據隔離。
Threadlocal
底層是通過threadlocalMap
進行存儲鍵值 每個ThreadLocal
類創建一個Map,然后用線程的ID作為Map的key,實例對象作為Map的value,這樣就能達到各個線程的值隔離的效果。
ThreadLocal
的作用是提供線程內的局部變量,這種變量在線程的生命周期內起作用,減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的復雜度。