本文基于 jdk1.8 和 netty 4.1.46 坝撑, jdk 這些年版本迭代的比較快静秆,每個版本中部分 api 都有優(yōu)化,netty 同樣也是非逞怖睿活躍抚笔,小版本迭非常快侨拦。所以討論 api 不指定版本是沒有意義的殊橙。
一、jdk1.8 ThreadLocal 原理
每個Thread實例都包含一個ThreadLocalMap的引用狱从,ThreadLocalMap 中有Entry數(shù)組膨蛮,Entry 的key (ThreadLocal 本身) 是若引用,類的關(guān)系入下圖
考慮如下問題:
1季研、因為 Entry 的 key (ThreadLocal 本身) 是弱引用敞葛,所以沒有強引用指向,只能生存到下次垃圾回收之前(所以ThreadLocal 盡量設(shè)置成類變量)与涡,那么如何處理 key 被回收了的Entry呢?
2惹谐、既然是Entry數(shù)組,初始大小是多少递沪?豺鼻,set的時候超過長度如何擴容综液?款慨,get的時候如何定位數(shù)組的index? 。
帶著這幾個問題我們分析一下代碼谬莹。
1檩奠、ThreadLocal 的初始化:
用全局靜態(tài)變量 nextHashCode 生成 ThreadLocal 的hash值桩了。
threadLocalHashCode 就是要用來定位 Entry 數(shù)組index的。 后面會講到
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) 方法
獲取當前 Thread 實例井誉,并且 Thread 實例中拿到 ThreadLocalMap 實例。
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 實例
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è)置擴容閥值 數(shù)組長度的 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)用一個重要的方法 replaceStaleEntry
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//擴容
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); 這個方法。因為len的值是2的n次方(擴容也在原數(shù)組長度上乘2)蛮寂。經(jīng)過 & 運算后i的值肯定在len之內(nèi)蔽午,確保不會越界。但是有個問題酬蹋,可能會出現(xiàn)定位的位置沖突及老。所以需要for循環(huán)依次向后查找,直到找到一個為空的范抓,然后 tab[i] = new Entry(key, value);骄恶。這里就給get的時候定位index造成了問題。后邊講
在向后查找的過程中匕垫,如果 key 相等 那么直接替換 valeu 然后返回
if (k == key) {
e.value = value;
return;
}
如果 key 已經(jīng)被回收了叠蝇,那么調(diào)用一個重要的方法 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;
//向前找到第一個被回收了的 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 向后搜索過程中發(fā)現(xiàn)key相同的entry
if (k == key) {
//覆蓋并且和臟entry進行交換
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
//staleSlot 之前沒有被回收的 ThreadLocal
if (slotToExpunge == staleSlot)
slotToExpunge = i;
//搜索臟entry并進行清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
//如果向前未搜索到臟entry年缎,并且在向后查找過程遇到臟entry的話
//就以i為起點清理entiy
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
//如果不只staleSlot一個節(jié)點為臟的
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
-
2.1悔捶、向前搜索到臟的entry,并且向后搜索過程中發(fā)現(xiàn)key相同的entry 如圖(圖片來自來自網(wǎng)絡(luò))
-
2.2 向前搜索到臟的entry,向后搜索過程中 沒有發(fā)現(xiàn)發(fā)現(xiàn)key相同的entry 如圖 (圖片來自網(wǎng)絡(luò))
-
2.3 向前沒有發(fā)現(xiàn)臟的entry,向后發(fā)現(xiàn)了臟的entry,和相同的 key 如圖 (來自網(wǎng)絡(luò))
再來看一下進行清理的方法,while循環(huán)次數(shù)為log2(table.length)-1}单芜。如果遇到臟Entry那么蜕该,循環(huán)將重新開始,并且范圍為數(shù)組長度洲鸠,這塊我也不明白為啥堂淡。
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扒腕、清除當前臟entry
2绢淀、往后環(huán)形繼續(xù)查找,直到遇到table[i]==null時結(jié)束
3、如果在向后搜索過程中再次遇到臟entry瘾腰,同樣將其清理掉
4皆的、如果有hash沖突的情況,重新整理數(shù)組
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//1蹋盆、清除當前臟entry
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
//2.往后環(huán)形繼續(xù)查找,直到遇到table[i]==null時結(jié)束
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//3. 如果在向后搜索過程中再次遇到臟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 方法相對就簡單了
1、拿到當前線程召廷,的 ThreadLocalMap 對象凳厢。
2藤为、ThreadLocalMap 對象獲取 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需要釋放作煌,清理過程上文已經(jīng)將到
if (k == null)
expungeStaleEntry(i);
//解決位置沖突問題梗顺,所以需要向下查找
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
總結(jié):
1泡孩、因為 Entry 的 key 是若引用,所以如果沒有強引用指向寺谤,只能生存到下次垃圾回收之前仑鸥,那么如何處理key被回收了的Entry呢?
答案:當 ThreadLocal.set()時,如果發(fā)現(xiàn)位置沖突变屁,那么nextIndex(i, len)方法尋找空位眼俊,在尋找空位的時候,如果發(fā)現(xiàn)Entry 的 ThreadLocal 被回收了粟关,那么調(diào)用 replaceStaleEntry 進行替換和清理疮胖,如果沒有沖突那么調(diào)用 cleanSomeSlots 方法清理一定范圍臟Entry。
當 ThreadLocal.get 的時候如果發(fā)現(xiàn)位置沖突闷板,那么nextIndex(i, len)方法尋找符合的Entry,在尋找空位的時候澎灸,如果發(fā)現(xiàn)Entry 的 ThreadLocal 被回收了,那么調(diào)用expungeStaleEntry清除被回收的Entry2遮晚、既然是Entry數(shù)組性昭,初始大小是多少?
private static final int INITIAL_CAPACITY = 16;
- 3县遣、set的時候超過長度如何擴容糜颠?
當ThreadLocal.set() 的 Entry 數(shù)量大于擴容閥值。threshold = len * 2 / 3; 萧求。調(diào)用rehash() 方法其兴。
private void rehash() {
//清除ThreadLocal已經(jīng)被回收的 Entry
expungeStaleEntries();
//經(jīng)過清理后 size >= threshold - threshold / 4 那么resize();擴容
if (size >= threshold - threshold / 4)
resize();
}
resize() 的過程就不難了,
1夸政、首先將table 大小擴大一倍元旬。
2、發(fā)現(xiàn) ThreadLocal 被回收了那么,value 復(fù)制為null 幫助gc
3法绵、copy數(shù)組,重新計算數(shù)組下表
4酪碘、設(shè)置下次擴容的閥值
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ù)組的index? .
通過:int i = key.threadLocalHashCode & (table.length - 1);
若位置沖突: i = nextIndex(i, len);
以上可見,大師 Doug Lea兴垦,為了解決弱引用被回收問題徙赢,和位置沖突問題,耍了一套獨孤九劍探越。
二狡赐、netty4.1.46 FastThreadLocal 原理
Netty為了在某些場景下提高性能,改進了jdk ThreadLocal钦幔,Netty實現(xiàn)的FastThreadLocal 優(yōu)化了Java 原生 ThreadLocal 的訪問速度枕屉,存儲速度。避免了檢測弱引用帶來的 value 回收難問題鲤氢,和數(shù)組位置沖突帶來的線性查找問題搀擂,解決這些問題并不是沒有代價,
Netty實現(xiàn)的 FastThreadLocal 底層也是通過數(shù)組存儲 value 對象卷玉,與Java原生ThreadLocal使用自身作為Entry的key不同哨颂,F(xiàn)astThreadLocal通過保存數(shù)組的全局唯一下標,實現(xiàn)了對value的快速訪問相种。同時FastThreadLocal 也實現(xiàn)了清理對象的方法威恼,下面會對這些內(nèi)容進行分別介紹。
為了敘述方便寝并,下文使用FTL指代Netty的FastThreadLocal箫措,使用TL指代Java原生ThreadLocal。
1衬潦、類的結(jié)構(gòu)
1.1 FastThreadLocal 類的uml圖蒂破,
1.2 FastThreadLocalThread
cleanupFastThreadLocals 字段在 4.1.46 的版本中已經(jīng)沒有在用到了
/**
* true,表示FTL會在線程結(jié)束時被主動清理 見 FastThreadLocalRunnable 類
* false别渔,需要將FTL放入后臺清理線程的隊列中
*/
// This will be set to true if we have a chance to wrap the Runnable.
//這個字段則用于標識該線程在結(jié)束時是否會主動清理FTL
private final boolean cleanupFastThreadLocals;
//次對象將在 第一次 FastThreadLocal.get 和 FastThreadLocal.set 時候創(chuàng)建
private InternalThreadLocalMap threadLocalMap;
public FastThreadLocalThread(Runnable target) {
super(FastThreadLocalRunnable.wrap(target));
cleanupFastThreadLocals = true;
}
1.3 InternalThreadLocalMap
FastThreadLocalThread.threadLocalMap 是 InternalThreadLocalMap 對象實例附迷。在第一次獲取FTL數(shù)據(jù)時,會初始化FastThreadLocalThread.threadLocalMap哎媚,調(diào)用的構(gòu)造函數(shù)如下:
private InternalThreadLocalMap() {
//為了簡便喇伯,InternalThreadLocalMap父類
//UnpaddedInternalThreadLocalMap不展開介紹
super(newIndexedVariableTable());
}
//默認的數(shù)組大小為32,且使用UNSET對象填充數(shù)組
//如果下標處數(shù)據(jù)為UNSET拨与,則表示沒有數(shù)據(jù)
private static Object[] newIndexedVariableTable() {
Object[] array = new Object[32];
Arrays.fill(array, UNSET);
return array;
}
為了避免寫時候影響同一cpu緩沖行的其他數(shù)據(jù)并發(fā)訪問稻据,其使用了緩存行填充技術(shù) (cpu 緩沖行填充),在類定義中聲明了如下long字段進行填充,具體可以參考https://blog.csdn.net/qq_27428109/article/details/74781774捻悯,在Java8中則可以使用@sun.misc.Contended注解避免偽共享問題匆赃。
//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;
上面我們說到FTL保存了數(shù)組下標,F(xiàn)TL使用的數(shù)組下標是InternalThreadLocalMap中的靜態(tài)變量nextIndex統(tǒng)一遞增生成的:
static final AtomicInteger nextIndex = new AtomicInteger();
public static int nextVariableIndex() {
//Netty中所有FTL數(shù)組下標都是通過遞增這個靜態(tài)變量實現(xiàn)的
//采用靜態(tài)變量生成所有FTL元素在數(shù)組中的下標會造成一個問題今缚,
//會造成InternalThreadLocalMap中數(shù)組不必要的自動擴容
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ù)組下標算柳,因為InternalThreadLocalMap.nextVariableIndex() 使用靜態(tài)域 nextIndex 遞增維護所有FTL的下標,會造成后面實例化的 FTL 下標過大姓言,如果FTL下標大于其對應(yīng) FastThreadLocalThread.threadLocalMap 數(shù)組的長度瞬项,會進行數(shù)組的自動擴容,如下:
private void expandIndexedVariableTableAndSet(int index, Object value) {
Object[] oldArray = indexedVariables;
final int oldCapacity = oldArray.length;
//下面復(fù)雜的實現(xiàn)是為了將newCapacity規(guī)范為最接近的一個2的指數(shù),
//這段代碼在早期的 jdk HashMap 中見過
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ù):
有兩個重要的下標域何荚,F(xiàn)TL不僅在FastThreadLocalThread.threadLocalMap中保存了用戶實際使用的value(在數(shù)組中的下標為index)囱淋,還在數(shù)組中保存為了實現(xiàn)清理記錄的相關(guān)數(shù)據(jù),也即下標variablesToRemoveIndex,一般情況 variablesToRemoveIndex = 0餐塘。因為variablesToRemoveIndex 是靜態(tài)變量妥衣,所以全局唯一。
//如果在該FTL中放入了數(shù)據(jù)戒傻,也就實際調(diào)用了其set或get函數(shù)称鳞,會在
//該FastThreadLocalThread.threadLocalMap數(shù)組的
// variablesToRemoveIndex下標處放置一個IdentityHashMap,
//并將該FTL放入IdentityHashMap中稠鼻,在后續(xù)清理時會取出
//variablesToRemoveIndex下標處的IdentityHashMap進行清理
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
//在threadLocalMap數(shù)組中存放實際數(shù)據(jù)的下標
private final int index;
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
- 用戶可擴展的函數(shù):
//初始化 value 函數(shù)
protected V initialValue() throws Exception {
return null;
}
//讓使用者在該FTL被移除時可以有機會做些操作冈止。
protected void onRemoval(@SuppressWarnings("UnusedParameters") V value) throws Exception { }
1.5 FastThreadLocalThread
cleanupFastThreadLocals 字段在 4.1 的最新版本中已經(jīng)沒有在用到了
/**
* true,表示FTL會在線程結(jié)束時被主動清理 見 FastThreadLocalRunnable 類
* false候齿,需要將FTL放入后臺清理線程的隊列中
*/
// This will be set to true if we have a chance to wrap the Runnable.
//這個字段則用于標識該線程在結(jié)束時是否會主動清理FTL
private final boolean cleanupFastThreadLocals;
//次對象將在 第一次 FastThreadLocal.get 和 FastThreadLocal.set 時候創(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) {
//獲取當前線程的 InternalThreadLocalMap , 如果當前線程為FastThreadLocalThread,那么直接通過threadLocalMap引用獲取
//否則通過 jdk 原生的 threadLocal 獲取
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
//FastThreadLocal 對應(yīng)的 index 下標的 value 替換成新的 value
setKnownNotUnset(threadLocalMap, value);
} else {
//如果放置的對象為UNSET慌盯,則表示清理周霉,會對該FTL進行清理,類似毒丸對象
remove();
}
}
這里擴容方會調(diào)用 InternalThreadLocalMap.expandIndexedVariableTableAndSet
private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
//在數(shù)組下標index處放置實際對象亚皂,如果index大于數(shù)組length俱箱,會進行數(shù)組擴容.
if (threadLocalMap.setIndexedVariable(index, value)) {
//放置成功之后,將該FTL加入到 variablesToRemoveIndex 下標的
//IdentityHashMap灭必,等待后續(xù)清理
addToVariablesToRemove(threadLocalMap, this);
}
}
/**
* 該FTL加入到variablesToRemoveIndex下標的IdentityHashMap
* IdentityHashMap的特性可以保證同一個實例不會被多次加入到該位置
*/
@SuppressWarnings("unchecked")
private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
//獲取 variablesToRemoveIndex下標處的 IdentityHashMap
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
Set<FastThreadLocal<?>> variablesToRemove;
//如果是第一次獲取狞谱,則 variablesToRemoveIndex下標處的值為 UNSET
if (v == InternalThreadLocalMap.UNSET || v == null) {
//新建一個新的 IdentityHashMap 并
variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
//放入到下標variablesToRemoveIndex處
threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
} else {
variablesToRemove = (Set<FastThreadLocal<?>>) v;
}
//將該FTL放入該IdentityHashMap中
variablesToRemove.add(variable);
}
下面看InternalThreadLocalMap.get()實現(xiàn):
public static InternalThreadLocalMap get() {
Thread thread = Thread.currentThread();
//首先看當前 thread 是否為FastThreadLocalThread實例
//如果是的話,可以快速通過引用禁漓,獲取到其 threadLocalMap
if (thread instanceof FastThreadLocalThread) {
return fastGet((FastThreadLocalThread) thread);
} else {
//如果不是跟衅,則 jdk 原生慢速獲取到其 threadLocalMap
return slowGet();
}
}
2.2、數(shù)據(jù)的設(shè)置 get 方法
了解數(shù)據(jù)的設(shè)置set方法播歼,獲取就比較點單伶跷,代碼就不貼了。
- 1、獲取 ThreadLocalMap
- 2叭莫、直接通過索引取出對象
- 3蹈集、如果為空那么調(diào)用初始化方法初始化
3、 清理 FastThreadLocal 對象
3.1 主動清理
清理 FastThreadLocal 對象相關(guān)的代碼是在 FastThreadLocalThread 類雇初,和 FastThreadLocalRunnable 類中拢肆。
FastThreadLocalThread 的代碼見上文
FastThreadLocalRunnable.wrap 方法修飾的 Runnable,表示 FTL 會在線程結(jié)束時被主動清理抵皱,wrap 方法會把原 Runnable.run 方法放在 try 里善榛,然后在 finally 中調(diào)用 FastThreadLocal.removeAll() 方法辩蛋,該方法會對 FTL 進行清理呻畸,具體可看下面列出的源碼。
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會變成F astThreadLocalRunnable對象
//FastThreadLocalRunnable在run方法的finally會調(diào)用
//FastThreadLocal.removeAll();在線程結(jié)束時對FTL
//進行主動清理
static Runnable wrap(Runnable runnable) {
return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
}
}
3.2 被動清理在4.1.46版本中已經(jīng)被去掉了
// TODO: We need to find a better way to handle this.
我們需要找到更好的方法來處理這個問題伤为。
但是我目前在源碼中沒有找到,新的處理辦法据途,或者有知道的不吝賜教绞愚,或者這個也作為一個開放性問題吧。