jdk ThreadLocal 和 netty FastThreadLocal 的區(qū)別

本文基于 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)系入下圖

image.png

考慮如下問題:
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ò))


    clipboard.png
  • 2.2 向前搜索到臟的entry,向后搜索過程中 沒有發(fā)現(xiàn)發(fā)現(xiàn)key相同的entry 如圖 (圖片來自網(wǎng)絡(luò))


    clipboard.png
  • 2.3 向前沒有發(fā)現(xiàn)臟的entry,向后發(fā)現(xiàn)了臟的entry,和相同的 key 如圖 (來自網(wǎng)絡(luò))


    clipboard.png

再來看一下進行清理的方法,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清除被回收的Entry

  • 2遮晚、既然是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圖蒂破,
clipboard.png
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.
我們需要找到更好的方法來處理這個問題伤为。
但是我目前在源碼中沒有找到,新的處理辦法据途,或者有知道的不吝賜教绞愚,或者這個也作為一個開放性問題吧。

clipboard.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颖医,一起剝皮案震驚了整個濱河市位衩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熔萧,老刑警劉巖糖驴,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異佛致,居然都是意外死亡贮缕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門俺榆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來感昼,“玉大人,你說我怎么就攤上這事罐脊《ㄉぃ” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵萍桌,是天一觀的道長蜕乡。 經(jīng)常有香客問我,道長梗夸,這世上最難降的妖魔是什么层玲? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上辛块,老公的妹妹穿的比我還像新娘畔派。我一直安慰自己,他們只是感情好润绵,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布线椰。 她就那樣靜靜地躺著,像睡著了一般尘盼。 火紅的嫁衣襯著肌膚如雪憨愉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天卿捎,我揣著相機與錄音配紫,去河邊找鬼。 笑死午阵,一個胖子當著我的面吹牛躺孝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播底桂,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼植袍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了籽懦?” 一聲冷哼從身側(cè)響起于个,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎暮顺,沒想到半個月后厅篓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡拖云,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年贷笛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宙项。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡乏苦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出尤筐,到底是詐尸還是另有隱情汇荐,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布盆繁,位于F島的核電站掀淘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏油昂。R本人自食惡果不足惜革娄,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一倾贰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拦惋,春花似錦匆浙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至言秸,卻和暖如春软能,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背举畸。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工查排, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俱恶。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓雹嗦,卻偏偏與公主長得像范舀,于是被迫代替她去往敵國和親合是。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容