本文基于 jdk1.8 和 netty 4.1.46 , jdk 這些年版本迭代的比較快,每個(gè)版本中部分 api 都有優(yōu)化,netty 同樣也是非?;钴S,小版本迭非常快。所以討論 api 不指定版本是沒(méi)有意義的。
一、jdk1.8 ThreadLocal 原理
每個(gè)Thread實(shí)例都包含一個(gè)ThreadLocalMap的引用,ThreadLocalMap 中有Entry數(shù)組,Entry 的key (ThreadLocal 本身) 是若引用,類的關(guān)系入下圖
考慮如下問(wèn)題:
1、因?yàn)?Entry 的 key (ThreadLocal 本身) 是弱引用,所以沒(méi)有強(qiáng)引用指向,只能生存到下次垃圾回收之前(所以ThreadLocal 盡量設(shè)置成類變量),那么如何處理 key 被回收了的Entry呢?
2、既然是Entry數(shù)組,初始大小是多少?,set的時(shí)候超過(guò)長(zhǎng)度如何擴(kuò)容?,get的時(shí)候如何定位數(shù)組的index? 。
帶著這幾個(gè)問(wèn)題我們分析一下代碼。
1、ThreadLocal 的初始化:
用全局靜態(tài)變量 nextHashCode 生成 ThreadLocal 的hash值。
threadLocalHashCode 就是要用來(lái)定位 Entry 數(shù)組index的。 后面會(huì)講到
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
2、ThreadLocal.set(T value) 方法
獲取當(dāng)前 Thread 實(shí)例,并且 Thread 實(shí)例中拿到 ThreadLocalMap 實(shí)例。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
如果 map 為空則調(diào)用 createMap 創(chuàng)建 ThreadLocalMap 實(shí)例
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化Entry數(shù)組
table = new Entry[INITIAL_CAPACITY];
//定位數(shù)組 index
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
//設(shè)置擴(kuò)容閥值 數(shù)組長(zhǎng)度的 2/3
setThreshold(INITIAL_CAPACITY);
}
如果map非空,調(diào)用set方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//定位數(shù)組 index
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果查詢的,結(jié)果 key 相等,那么直接替換 valeu
if (k == key) {
e.value = value;
return;
}
//如果查詢的,結(jié)果 key 已經(jīng)被回收了,那么調(diào)用一個(gè)重要的方法 replaceStaleEntry
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//擴(kuò)容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
注意 int i = key.threadLocalHashCode & (len-1); 這個(gè)方法。因?yàn)閘en的值是2的n次方(擴(kuò)容也在原數(shù)組長(zhǎng)度上乘2)。經(jīng)過(guò) & 運(yùn)算后i的值肯定在len之內(nèi),確保不會(huì)越界。但是有個(gè)問(wèn)題,可能會(huì)出現(xiàn)定位的位置沖突。所以需要for循環(huán)依次向后查找,直到找到一個(gè)為空的,然后 tab[i] = new Entry(key, value);。這里就給get的時(shí)候定位index造成了問(wèn)題。后邊講
在向后查找的過(guò)程中,如果 key 相等 那么直接替換 valeu 然后返回
if (k == key) {
e.value = value;
return;
}
如果 key 已經(jīng)被回收了,那么調(diào)用一個(gè)重要的方法 replaceStaleEntry
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
replaceStaleEntry 方法就是替換已經(jīng)被回收了的 ThreadLocal 變量。
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
//向前找到第一個(gè)被回收了的 ThreadLocal
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
//向后查找
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//1.1 向后搜索過(guò)程中發(fā)現(xiàn)key相同的entry
if (k == key) {
//覆蓋并且和臟entry進(jìn)行交換
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
//staleSlot 之前沒(méi)有被回收的 ThreadLocal
if (slotToExpunge == staleSlot)
slotToExpunge = i;
//搜索臟entry并進(jìn)行清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
//如果向前未搜索到臟entry,并且在向后查找過(guò)程遇到臟entry的話
//就以i為起點(diǎn)清理entiy
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
//如果不只staleSlot一個(gè)節(jié)點(diǎn)為臟的
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
-
2.1、向前搜索到臟的entry,并且向后搜索過(guò)程中發(fā)現(xiàn)key相同的entry 如圖(圖片來(lái)自來(lái)自網(wǎng)絡(luò))
clipboard.png -
2.2 向前搜索到臟的entry,向后搜索過(guò)程中 沒(méi)有發(fā)現(xiàn)發(fā)現(xiàn)key相同的entry 如圖 (圖片來(lái)自網(wǎng)絡(luò))
clipboard.png -
2.3 向前沒(méi)有發(fā)現(xiàn)臟的entry,向后發(fā)現(xiàn)了臟的entry,和相同的 key 如圖 (來(lái)自網(wǎng)絡(luò))
clipboard.png
再來(lái)看一下進(jìn)行清理的方法,while循環(huán)次數(shù)為log2(table.length)-1}。如果遇到臟Entry那么,循環(huán)將重新開始,并且范圍為數(shù)組長(zhǎng)度,這塊我也不明白為啥。
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) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
expungeStaleEntry 方法,
步驟
1、清除當(dāng)前臟entry
2、往后環(huán)形繼續(xù)查找,直到遇到table[i]==null時(shí)結(jié)束
3、如果在向后搜索過(guò)程中再次遇到臟entry,同樣將其清理掉
4、如果有hash沖突的情況,重新整理數(shù)組
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//1、清除當(dāng)前臟entry
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
//2.往后環(huán)形繼續(xù)查找,直到遇到table[i]==null時(shí)結(jié)束
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//3. 如果在向后搜索過(guò)程中再次遇到臟entry,同樣將其清理掉
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
//4、如果有位置沖突的情況,重新整理數(shù)組
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
3、ThreadLocal.get(T value) 方法,如果了解了 set 方法,那么get 方法相對(duì)就簡(jiǎn)單了
1、拿到當(dāng)前線程,的 ThreadLocalMap 對(duì)象。
2、ThreadLocalMap 對(duì)象獲取 Entry
3、 如果 ThreadLocalMap 為空那么調(diào)用 setInitialValue 方法,此方法內(nèi)部 調(diào)用了ThreadLocal.set方法
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
//遇到臟key需要釋放,清理過(guò)程上文已經(jīng)將到
if (k == null)
expungeStaleEntry(i);
//解決位置沖突問(wèn)題,所以需要向下查找
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
總結(jié):
1、因?yàn)?Entry 的 key 是若引用,所以如果沒(méi)有強(qiáng)引用指向,只能生存到下次垃圾回收之前,那么如何處理key被回收了的Entry呢?
答案:當(dāng) ThreadLocal.set()時(shí),如果發(fā)現(xiàn)位置沖突,那么nextIndex(i, len)方法尋找空位,在尋找空位的時(shí)候,如果發(fā)現(xiàn)Entry 的 ThreadLocal 被回收了,那么調(diào)用 replaceStaleEntry 進(jìn)行替換和清理,如果沒(méi)有沖突那么調(diào)用 cleanSomeSlots 方法清理一定范圍臟Entry。
當(dāng) ThreadLocal.get 的時(shí)候如果發(fā)現(xiàn)位置沖突,那么nextIndex(i, len)方法尋找符合的Entry,在尋找空位的時(shí)候,如果發(fā)現(xiàn)Entry 的 ThreadLocal 被回收了,那么調(diào)用expungeStaleEntry清除被回收的Entry2、既然是Entry數(shù)組,初始大小是多少?
private static final int INITIAL_CAPACITY = 16;
- 3、set的時(shí)候超過(guò)長(zhǎng)度如何擴(kuò)容?
當(dāng)ThreadLocal.set() 的 Entry 數(shù)量大于擴(kuò)容閥值。threshold = len * 2 / 3; 。調(diào)用rehash() 方法。
private void rehash() {
//清除ThreadLocal已經(jīng)被回收的 Entry
expungeStaleEntries();
//經(jīng)過(guò)清理后 size >= threshold - threshold / 4 那么resize();擴(kuò)容
if (size >= threshold - threshold / 4)
resize();
}
resize() 的過(guò)程就不難了,
1、首先將table 大小擴(kuò)大一倍。
2、發(fā)現(xiàn) ThreadLocal 被回收了那么,value 復(fù)制為null 幫助gc
3、copy數(shù)組,重新計(jì)算數(shù)組下表
4、設(shè)置下次擴(kuò)容的閥值
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
- 4、get的時(shí)候如何定位數(shù)組的index? .
通過(guò):int i = key.threadLocalHashCode & (table.length - 1);
若位置沖突: i = nextIndex(i, len);
以上可見,大師 Doug Lea,為了解決弱引用被回收問(wèn)題,和位置沖突問(wèn)題,耍了一套獨(dú)孤九劍。
二、netty4.1.46 FastThreadLocal 原理
Netty為了在某些場(chǎng)景下提高性能,改進(jìn)了jdk ThreadLocal,Netty實(shí)現(xiàn)的FastThreadLocal 優(yōu)化了Java 原生 ThreadLocal 的訪問(wèn)速度,存儲(chǔ)速度。避免了檢測(cè)弱引用帶來(lái)的 value 回收難問(wèn)題,和數(shù)組位置沖突帶來(lái)的線性查找問(wèn)題,解決這些問(wèn)題并不是沒(méi)有代價(jià),
Netty實(shí)現(xiàn)的 FastThreadLocal 底層也是通過(guò)數(shù)組存儲(chǔ) value 對(duì)象,與Java原生ThreadLocal使用自身作為Entry的key不同,F(xiàn)astThreadLocal通過(guò)保存數(shù)組的全局唯一下標(biāo),實(shí)現(xiàn)了對(duì)value的快速訪問(wèn)。同時(shí)FastThreadLocal 也實(shí)現(xiàn)了清理對(duì)象的方法,下面會(huì)對(duì)這些內(nèi)容進(jìn)行分別介紹。
為了敘述方便,下文使用FTL指代Netty的FastThreadLocal,使用TL指代Java原生ThreadLocal。
1、類的結(jié)構(gòu)
1.1 FastThreadLocal 類的uml圖,
1.2 FastThreadLocalThread
cleanupFastThreadLocals 字段在 4.1.46 的版本中已經(jīng)沒(méi)有在用到了
/**
* true,表示FTL會(huì)在線程結(jié)束時(shí)被主動(dòng)清理 見 FastThreadLocalRunnable 類
* false,需要將FTL放入后臺(tái)清理線程的隊(duì)列中
*/
// This will be set to true if we have a chance to wrap the Runnable.
//這個(gè)字段則用于標(biāo)識(shí)該線程在結(jié)束時(shí)是否會(huì)主動(dòng)清理FTL
private final boolean cleanupFastThreadLocals;
//次對(duì)象將在 第一次 FastThreadLocal.get 和 FastThreadLocal.set 時(shí)候創(chuàng)建
private InternalThreadLocalMap threadLocalMap;
public FastThreadLocalThread(Runnable target) {
super(FastThreadLocalRunnable.wrap(target));
cleanupFastThreadLocals = true;
}
1.3 InternalThreadLocalMap
FastThreadLocalThread.threadLocalMap 是 InternalThreadLocalMap 對(duì)象實(shí)例。在第一次獲取FTL數(shù)據(jù)時(shí),會(huì)初始化FastThreadLocalThread.threadLocalMap,調(diào)用的構(gòu)造函數(shù)如下:
private InternalThreadLocalMap() {
//為了簡(jiǎn)便,InternalThreadLocalMap父類
//UnpaddedInternalThreadLocalMap不展開介紹
super(newIndexedVariableTable());
}
//默認(rèn)的數(shù)組大小為32,且使用UNSET對(duì)象填充數(shù)組
//如果下標(biāo)處數(shù)據(jù)為UNSET,則表示沒(méi)有數(shù)據(jù)
private static Object[] newIndexedVariableTable() {
Object[] array = new Object[32];
Arrays.fill(array, UNSET);
return array;
}
為了避免寫時(shí)候影響同一cpu緩沖行的其他數(shù)據(jù)并發(fā)訪問(wèn),其使用了緩存行填充技術(shù) (cpu 緩沖行填充),在類定義中聲明了如下long字段進(jìn)行填充,具體可以參考https://blog.csdn.net/qq_27428109/article/details/74781774,在Java8中則可以使用@sun.misc.Contended注解避免偽共享問(wèn)題。
//InternalThreadLocalMap
// Cache line padding (must be public)
// With CompressedOops enabled, an instance of this class should occupy at least 128 bytes.
public long rp1, rp2, rp3, rp4, rp5, rp6, rp7, rp8, rp9;
上面我們說(shuō)到FTL保存了數(shù)組下標(biāo),F(xiàn)TL使用的數(shù)組下標(biāo)是InternalThreadLocalMap中的靜態(tài)變量nextIndex統(tǒng)一遞增生成的:
static final AtomicInteger nextIndex = new AtomicInteger();
public static int nextVariableIndex() {
//Netty中所有FTL數(shù)組下標(biāo)都是通過(guò)遞增這個(gè)靜態(tài)變量實(shí)現(xiàn)的
//采用靜態(tài)變量生成所有FTL元素在數(shù)組中的下標(biāo)會(huì)造成一個(gè)問(wèn)題,
//會(huì)造成InternalThreadLocalMap中數(shù)組不必要的自動(dòng)擴(kuò)容
int index = nextIndex.getAndIncrement();
if (index < 0) {
nextIndex.decrementAndGet();
throw new IllegalStateException("too many thread-local indexed variables");
}
return index;
}
InternalThreadLocalMap.nextVariableIndex()方法獲取FTL在該FastThreadLocalThread.threadLocalMap數(shù)組下標(biāo),因?yàn)镮nternalThreadLocalMap.nextVariableIndex() 使用靜態(tài)域 nextIndex 遞增維護(hù)所有FTL的下標(biāo),會(huì)造成后面實(shí)例化的 FTL 下標(biāo)過(guò)大,如果FTL下標(biāo)大于其對(duì)應(yīng) FastThreadLocalThread.threadLocalMap 數(shù)組的長(zhǎng)度,會(huì)進(jìn)行數(shù)組的自動(dòng)擴(kuò)容,如下:
private void expandIndexedVariableTableAndSet(int index, Object value) {
Object[] oldArray = indexedVariables;
final int oldCapacity = oldArray.length;
//下面復(fù)雜的實(shí)現(xiàn)是為了將newCapacity規(guī)范為最接近的一個(gè)2的指數(shù),
//這段代碼在早期的 jdk HashMap 中見過(guò)
int newCapacity = index;
newCapacity |= newCapacity >>> 1;
newCapacity |= newCapacity >>> 2;
newCapacity |= newCapacity >>> 4;
newCapacity |= newCapacity >>> 8;
newCapacity |= newCapacity >>> 16;
newCapacity ++;
Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
newArray[index] = value;
indexedVariables = newArray;
}
1.4 FastThreadLocal
- 構(gòu)造函數(shù):
有兩個(gè)重要的下標(biāo)域,F(xiàn)TL不僅在FastThreadLocalThread.threadLocalMap中保存了用戶實(shí)際使用的value(在數(shù)組中的下標(biāo)為index),還在數(shù)組中保存為了實(shí)現(xiàn)清理記錄的相關(guān)數(shù)據(jù),也即下標(biāo)variablesToRemoveIndex,一般情況 variablesToRemoveIndex = 0。因?yàn)関ariablesToRemoveIndex 是靜態(tài)變量,所以全局唯一。
//如果在該FTL中放入了數(shù)據(jù),也就實(shí)際調(diào)用了其set或get函數(shù),會(huì)在
//該FastThreadLocalThread.threadLocalMap數(shù)組的
// variablesToRemoveIndex下標(biāo)處放置一個(gè)IdentityHashMap,
//并將該FTL放入IdentityHashMap中,在后續(xù)清理時(shí)會(huì)取出
//variablesToRemoveIndex下標(biāo)處的IdentityHashMap進(jìn)行清理
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
//在threadLocalMap數(shù)組中存放實(shí)際數(shù)據(jù)的下標(biāo)
private final int index;
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
- 用戶可擴(kuò)展的函數(shù):
//初始化 value 函數(shù)
protected V initialValue() throws Exception {
return null;
}
//讓使用者在該FTL被移除時(shí)可以有機(jī)會(huì)做些操作。
protected void onRemoval(@SuppressWarnings("UnusedParameters") V value) throws Exception { }
1.5 FastThreadLocalThread
cleanupFastThreadLocals 字段在 4.1 的最新版本中已經(jīng)沒(méi)有在用到了
/**
* true,表示FTL會(huì)在線程結(jié)束時(shí)被主動(dòng)清理 見 FastThreadLocalRunnable 類
* false,需要將FTL放入后臺(tái)清理線程的隊(duì)列中
*/
// This will be set to true if we have a chance to wrap the Runnable.
//這個(gè)字段則用于標(biāo)識(shí)該線程在結(jié)束時(shí)是否會(huì)主動(dòng)清理FTL
private final boolean cleanupFastThreadLocals;
//次對(duì)象將在 第一次 FastThreadLocal.get 和 FastThreadLocal.set 時(shí)候創(chuàng)建
private InternalThreadLocalMap threadLocalMap;
public FastThreadLocalThread(Runnable target) {
super(FastThreadLocalRunnable.wrap(target));
cleanupFastThreadLocals = true;
}
2 數(shù)據(jù) set 和 get
2.1、數(shù)據(jù)的設(shè)置 set 方法
public final void set(V value) {
//判斷設(shè)置的 value 值是否是缺省值
if (value != InternalThreadLocalMap.UNSET) {
//獲取當(dāng)前線程的 InternalThreadLocalMap , 如果當(dāng)前線程為FastThreadLocalThread,那么直接通過(guò)threadLocalMap引用獲取
//否則通過(guò) jdk 原生的 threadLocal 獲取
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
//FastThreadLocal 對(duì)應(yīng)的 index 下標(biāo)的 value 替換成新的 value
setKnownNotUnset(threadLocalMap, value);
} else {
//如果放置的對(duì)象為UNSET,則表示清理,會(huì)對(duì)該FTL進(jìn)行清理,類似毒丸對(duì)象
remove();
}
}
這里擴(kuò)容方會(huì)調(diào)用 InternalThreadLocalMap.expandIndexedVariableTableAndSet
private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
//在數(shù)組下標(biāo)index處放置實(shí)際對(duì)象,如果index大于數(shù)組length,會(huì)進(jìn)行數(shù)組擴(kuò)容.
if (threadLocalMap.setIndexedVariable(index, value)) {
//放置成功之后,將該FTL加入到 variablesToRemoveIndex 下標(biāo)的
//IdentityHashMap,等待后續(xù)清理
addToVariablesToRemove(threadLocalMap, this);
}
}
/**
* 該FTL加入到variablesToRemoveIndex下標(biāo)的IdentityHashMap
* IdentityHashMap的特性可以保證同一個(gè)實(shí)例不會(huì)被多次加入到該位置
*/
@SuppressWarnings("unchecked")
private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
//獲取 variablesToRemoveIndex下標(biāo)處的 IdentityHashMap
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
Set<FastThreadLocal<?>> variablesToRemove;
//如果是第一次獲取,則 variablesToRemoveIndex下標(biāo)處的值為 UNSET
if (v == InternalThreadLocalMap.UNSET || v == null) {
//新建一個(gè)新的 IdentityHashMap 并
variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
//放入到下標(biāo)variablesToRemoveIndex處
threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
} else {
variablesToRemove = (Set<FastThreadLocal<?>>) v;
}
//將該FTL放入該IdentityHashMap中
variablesToRemove.add(variable);
}
下面看InternalThreadLocalMap.get()實(shí)現(xiàn):
public static InternalThreadLocalMap get() {
Thread thread = Thread.currentThread();
//首先看當(dāng)前 thread 是否為FastThreadLocalThread實(shí)例
//如果是的話,可以快速通過(guò)引用,獲取到其 threadLocalMap
if (thread instanceof FastThreadLocalThread) {
return fastGet((FastThreadLocalThread) thread);
} else {
//如果不是,則 jdk 原生慢速獲取到其 threadLocalMap
return slowGet();
}
}
2.2、數(shù)據(jù)的設(shè)置 get 方法
了解數(shù)據(jù)的設(shè)置set方法,獲取就比較點(diǎn)單,代碼就不貼了。
- 1、獲取 ThreadLocalMap
- 2、直接通過(guò)索引取出對(duì)象
- 3、如果為空那么調(diào)用初始化方法初始化
3、 清理 FastThreadLocal 對(duì)象
3.1 主動(dòng)清理
清理 FastThreadLocal 對(duì)象相關(guān)的代碼是在 FastThreadLocalThread 類,和 FastThreadLocalRunnable 類中。
FastThreadLocalThread 的代碼見上文
FastThreadLocalRunnable.wrap 方法修飾的 Runnable,表示 FTL 會(huì)在線程結(jié)束時(shí)被主動(dòng)清理,wrap 方法會(huì)把原 Runnable.run 方法放在 try 里,然后在 finally 中調(diào)用 FastThreadLocal.removeAll() 方法,該方法會(huì)對(duì) FTL 進(jìn)行清理,具體可看下面列出的源碼。
final class FastThreadLocalRunnable implements Runnable {
private final Runnable runnable;
private FastThreadLocalRunnable(Runnable runnable) {
this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");
}
@Override
public void run() {
//run方法放在try里,然后在finally中調(diào)用FastThreadLocal.removeAll()方法
try {
runnable.run();
} finally {
FastThreadLocal.removeAll();
}
}
//被wrap的 Runable會(huì)變成F astThreadLocalRunnable對(duì)象
//FastThreadLocalRunnable在run方法的finally會(huì)調(diào)用
//FastThreadLocal.removeAll();在線程結(jié)束時(shí)對(duì)FTL
//進(jìn)行主動(dòng)清理
static Runnable wrap(Runnable runnable) {
return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
}
}
3.2 被動(dòng)清理在4.1.46版本中已經(jīng)被去掉了
// TODO: We need to find a better way to handle this.
我們需要找到更好的方法來(lái)處理這個(gè)問(wèn)題。
但是我目前在源碼中沒(méi)有找到,新的處理辦法,或者有知道的不吝賜教,或者這個(gè)也作為一個(gè)開放性問(wèn)題吧。