//咱們只須要實例化一次,做爲key就夠了,或者使用靜態。
private ThreadLocal myThreadLoca<String>l = new ThreadLocal();
// 寫入
myThreadLocal.set("A thread local value");
// 讀取
String threadLocalValue = myThreadLocal.get();
複製代碼
在同步機制中,經過對象的鎖機制保證同一時間只有一個線程訪問變量,實現串行化.這時該變量是多個線程共享的,使用同步機制要求程序縝密地分析何時對變量進行讀/寫、何時須要鎖定某個對象、何時釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。java
給每一個線程創建副本,互不干擾.數組
對於多線程資源共享的問題.安全
同步機制採用「以時間換空間」的方式:訪問串行化,對象共享化 ; ThreadLocaI採用了「以空間換時間」的方式:訪問並行化,對象獨享化。數據結構
前者僅提供一份變量,讓不一樣的線程排隊訪問;然後者爲每一個線程都提供了一份變量,所以能夠同時訪問而互不影響。多線程
// hash code
private final int threadLocalHashCode = nextHashCode();
// AtomicInteger類型,從0開始
private static AtomicInteger nextHashCode =
new AtomicInteger();
// hash code每次增長1640531527
private static final int HASH_INCREMENT = 0x61c88647;
// 下一個hash code
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
複製代碼
ThreadLocal的hashcode(threadLocalHashCode)是從0開始,每新建一個ThreadLocal,對應的hashcode就加0x61c88647.工具
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 獲取當前線程的ThreadLocalMap
// 當前線程的ThreadLocalMap不爲空則調用set方法, this爲調用該方法的ThreadLocal對象
if (map != null)
map.set(this, value);
// map爲空則調用createMap方法建立一個新的ThreadLocalMap, 並新建一個Entry放入該
// ThreadLocalMap, 調用set方法的ThreadLocal和傳入的value做爲該Entry的key和value
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 返回線程t的threadLocals屬性,類型是ThreadLocalMap
}
複製代碼
注意此處的threadLocals變量是一個ThreadLocalMap,是Thread的一個局部變量,所以它只與當前線程綁定。源碼分析
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變量.其實ThreadLocal類就像是一個工具類同樣,核心是其內部類ThreadLocalMap.this
ThreadLocalMap是一個自定義哈希映射,僅用於維護線程本地變量值。
ThreadLocalMap是ThreadLocal的內部類,主要有一個Entry數組,Entry的key爲ThreadLocal,value爲ThreadLocal對應的值。
每一個線程都有一個ThreadLocalMap類型的threadLocals變量。spa
// 初始容量16
private static final int INITIAL_CAPACITY = 16;
// entry數組
private Entry[] table;
// entry 元素個數
private int size = 0;
// 擴容閥值
private int threshold; // Default to 0
// 一次set是2/3 len
private void setThreshold(int len) { threshold = len * 2 / 3; }
//下面兩個方法能夠向前/後獲取座標.
private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0);}
private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1); }
複製代碼
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
複製代碼
結合示意看,entry中的key是一個弱引用.它的存在不會使得ThreadLocal變量豁免垃圾回收.當定義ThreadLocal變量的線程終止,沒有強引用關聯ThreadLocal變量,它就會被垃圾回收..net
說到底ThreadLocal變量只是一個key而已,能夠用它去各個線程本身的ThreadLocalMap中查詢,它自己並不存儲信息.
固然這會形成各個線程的ThreadLocalMap中出現key爲null的entry.這個在後邊內存泄漏會講到.
private void rehash() {
expungeStaleEntries(); // 調用expungeStaleEntries方法清理key爲null的Entry
// 若是清理後size超過閾值的3/4, 則進行擴容
if (size >= threshold - threshold / 4)
// 大小*2
// h = k.threadLocalHashCode & (newLen - 1);
resize();
}
複製代碼
cleanSomeSlots方法
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len); // 下一個索引位置
Entry e = tab[i];
if (e != null && e.get() == null) { // 遍歷到key爲null的元素
n = len; // 重置n的值
removed = true; // 標誌有移除元素
i = expungeStaleEntry(i); // 移除i位置及以後的key爲null的元素
}
} while ( (n >>>= 1) != 0);
return removed;
}
複製代碼
從 i 開始,清除key爲空的Entry,掃描次數是log2n
,當遍歷到一個key爲null的元素時,調用expungeStaleEntry清除,並將遍歷次數重置。 n的值多是當前元素個數size(從增長元素操做調過來).或者整個entry長度len(從replaceStaleEntry方法調過來). 官方給出的解釋是這個方法簡單、快速,而且效果不錯。
expungeStaleEntry 方法
// 從staleSlot開始, 清除key爲空的Entry, 並將不爲空的元素放到合適的位置,最後返回Entry爲空的位置
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null; // 將tab上staleSlot位置的對象清空
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len); // 遍歷下一個元素, 即(i+1)%len位置的元素
(e = tab[i]) != null; // 遍歷到Entry爲空時, 跳出循環並返回索引位置
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) { // 當前遍歷Entry的key爲空, 則將該位置的對象清空
e.value = null;
tab[i] = null;
size--;
} else { // 當前遍歷Entry的key不爲空
int h = k.threadLocalHashCode & (len - 1); // 從新計算該Entry的索引位置
if (h != i) { // 若是索引位置不爲當前索引位置i
tab[i] = null; // 則將i位置對象清空, 替當前Entry尋找正確的位置
// 若是h位置不爲null,則向後尋找當前Entry的位置
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
複製代碼
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 計算出索引的位置
// 從索引位置開始遍歷,因爲不是鏈表結構,所以經過nextIndex方法來尋找下一個索引位置
for (Entry e = tab[i];
e != null; // 當遍歷到的Entry爲空時結束遍歷
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get(); // 拿到Entry的key,也就是ThreadLocal
// 該Entry的key和傳入的key相等, 則用傳入的value替換掉原來的value
if (k == key) {
e.value = value;
return;
}
// 該Entry的key爲空, 則表明該Entry須要被清空,
// 調用replaceStaleEntry方法
if (k == null) {
// 該方法會繼續尋找傳入key的安放位置, 並清理掉key爲空的Entry
replaceStaleEntry(key, value, i);
return;
}
}
// 尋找到一個空位置, 則放置在該位置上
tab[i] = new Entry(key, value);
int sz = ++size;
// cleanSomeSlots是用來清理掉key爲空的Entry,若是此方法返回true,則表明至少清理
// 了1個元素, 則這次set必然不須要擴容, 若是此方法返回false則判斷sz是否大於閾值
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash(); // 擴容
}
複製代碼
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot; // 清除元素的開始位置(記錄索引位置最前面的)
// 向前遍歷,直到遇到Entry爲空
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i; // 記錄最後一個key爲null的索引位置
// Find either the key or trailing null slot of run, whichever
// occurs first
for (int i = nextIndex(staleSlot, len); // 向後遍歷,直到遇到Entry爲空
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 該Entry的key和傳入的key相等, 則將傳入的value替換掉該Entry的value
if (k == key) {
e.value = value;
// 將i位置和staleSlot位置的元素對換(staleSlot位置較前,是要清除的元素)
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 若是相等, 則表明上面的向前尋找key爲null的遍歷沒有找到,
// 即staleSlot位置前面的元素沒有須要清除的,此時將slotToExpunge設置爲i,
// 由於原staleSlot的元素已經被放到i位置了,這時位置i前面的元素都不須要清除
if (slotToExpunge == staleSlot)
slotToExpunge = i;
// 從slotToExpunge位置開始清除key爲空的Entry
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// 若是第一次遍歷到key爲null的元素,而且上面的向前尋找key爲null的遍歷沒有找到,
// 則將slotToExpunge設置爲當前的位置
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 若是key沒有找到,則新建一個Entry,放在staleSlot位置
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 若是slotToExpunge!=staleSlot,表明除了staleSlot位置還有其餘位置的元素須要清除
// 須要清除的定義:key爲null的Entry,調用cleanSomeSlots方法清除key爲null的Entry
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
複製代碼
ThreadLocal 是一個key,去各個線程本身的map裏取值,不必多個.
Java 中每一個線程都有與之關聯的Thread對象,Thread對象中有一個ThreadLocal.ThreadLocalMap類型的成員變量,該變量是一個Hash表.因此每一個線程都單獨維護這樣一個Hash表. 當ThreadLocal類型對象調用set方法時,這個set方法會使用當前線程維護的Hash表,把本身做爲key, set方法的參數做爲value插入到Hash表中.因爲每一個線程維護的Hash表是獨立的,所以在不一樣的Hash表中,key值即便相同也是沒問題的.
若是不使用static的ThreadLocal變量,那麼當定義ThreadLocal的類建立新的實例時候,會出現多個ThreadLocal.這在大多數時候是沒有意義的.
ThreadLocalMap使用ThreadLocal的弱引用做爲Entry的key,若是一個ThreadLocal沒有外部強引用來引用它,下一次系統GC時,這個ThreadLocal必然會被回收,這樣一來,ThreadLocalMap中就會出現key爲null的Entry,就沒有辦法訪問這些key爲null的Entry的value。
咱們上面介紹的get、set、remove等方法中,都會對key爲null的Entry進行清除(expungeStaleEntry方法,將Entry的value清空,等下一次垃圾回收時,這些Entry將會被完全回收)。
可是若是當前線程一直在運行,而且一直不執行get、set、remove方法,這些key爲null的Entry的value就會一直存在一條強引用練:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value,致使這些key爲null的Entry的value永遠沒法回收,形成內存泄漏。
爲了不這種狀況,咱們能夠在使用完ThreadLocal後,手動調用remove方法,以免出現內存泄漏。
因爲咱們通常把ThreadLocal變量聲明成static類型,延長了它的生命週期,若是因爲一些緣由,持有ThreadLocal變量聲明的類沒有被回收. 那麼確實全部使用這個ThreadLocal變量的線程中的ThreadLocalMap中的entry中的value都不會被回收(key != null). 這確實會形成問題,尤爲當你的工做線程是複用的,自己永遠不會終止,好比線程池/tomat的工做線程. 更糟糕的是,沒法回收的value對應的類自己,和它的類加載器也沒法被回收.這個類加載器全部加載的全部的類的數據會存留在永久區.形成永久區內存泄漏(permgen leak)
使用後remove()是一個好習慣.
InheritableThreadLocal類是ThreadLocal類的子類. 子線程會copy父線程的值,造成本身的副本(淺拷貝).
1.對於可變對象:
2.對於不可變對象: 不可變對象因爲每次都是新對象, 因此不管父線程初始化與否,子線程和父線程都互不影響。
從上面兩條結論可知,子線程只能經過修改可變性(Mutable)對象對主線程纔是可見的,即才能將修改傳遞給主線程,但這不是一種好的實踐,不建議使用,爲了保護線程的安全性,通常建議只傳遞不可變(Immuable)對象,即沒有狀態的對象。