netty系列之:給ThreadLocal插上夢(mèng)想的翅膀,詳解FastThreadLocal

簡(jiǎn)介

JDK中的ThreadLocal可以通過(guò)get方法來(lái)獲得跟當(dāng)前線程綁定的值。而這些值是存儲(chǔ)在ThreadLocal.ThreadLocalMap中的。而在ThreadLocalMap中底層的數(shù)據(jù)存儲(chǔ)是一個(gè)Entry數(shù)組中的。

那么從ThreadLocalMap中獲取數(shù)據(jù)的速度如何呢?速度有沒(méi)有可以優(yōu)化的空間呢?

一起來(lái)看看。

從ThreadLocalMap中獲取數(shù)據(jù)

ThreadLocalMap作為一個(gè)Map,它的底層數(shù)據(jù)存儲(chǔ)是一個(gè)Entry類型的數(shù)組:

private Entry[] table;

我們?cè)賮?lái)回顧一下ThreadLocal是怎么獲取數(shù)據(jù)的:

        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

首先根據(jù)ThreadLocal對(duì)象中的threadLocalHashCode跟table的長(zhǎng)度進(jìn)行取模運(yùn)算,得到要獲取的Entry在table中的位置,然后判斷位置Entry的key是否和要獲取的ThreadLocal對(duì)象一致。

如果一致,說(shuō)明獲取到了ThreadLocal綁定的對(duì)象,直接返回即可。

如果不一致,則需要再次進(jìn)行查找。

我們看下再次查找的邏輯:

        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;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

getEntryAfterMiss的邏輯是,先判斷Entry中的對(duì)象是否要獲取的對(duì)象,如果是則直接返回。

如果Entry中的對(duì)象為空,則觸發(fā)清除過(guò)期Entry的方法。否則的話計(jì)算出下一個(gè)要判斷的地址,再次進(jìn)行判斷,直到最終找到要找到的對(duì)象為止。

可以看到,如果第一次沒(méi)有找到要找到的對(duì)象的話,后面則可能會(huì)遍歷多次,從而造成執(zhí)行效率變低。

那么有沒(méi)有可以提升這個(gè)尋找速度的方法呢?答案是肯定的。

FastThreadLocal

之前我們提到了,Netty中的本地對(duì)象池技術(shù),netty為其創(chuàng)建了一個(gè)專門(mén)的類叫做Recycler。雖然Recycler中也使用到了ThreadLocal,但是Recycler使用的threadLocal并不是JDK自帶的ThreadLocal,而是FastThreadLocal。和它關(guān)聯(lián)的ThreadLocalMap叫做InternalThreadLocalMap,和它關(guān)聯(lián)的Thread叫做FastThreadLocalThread。netty中的類和JDK中的類的對(duì)應(yīng)關(guān)系如下:

netty中的對(duì)象 JDK中的對(duì)象
FastThreadLocalThread Thread
InternalThreadLocalMap ThreadLocal.ThreadLocalMap
FastThreadLocal ThreadLocal

我們先來(lái)看FastThreadLocalThread。不管它到底快不快,既然是Thread,那么自然就要繼承自JDK的Thread:

public class FastThreadLocalThread extends Thread

和Thread一樣,F(xiàn)astThreadLocalThread中也有一個(gè)ThreadLocalMap,叫做InternalThreadLocalMap,它是FastThreadLocalThread的private屬性:

private InternalThreadLocalMap threadLocalMap;

InternalThreadLocalMap中也有一個(gè)ThreadLocal對(duì)象,叫做slowThreadLocalMap,是在fastThreadLocalMap不生效的時(shí)候使用的。

接下來(lái)我們來(lái)看下這個(gè)ThreadLocalMap為什么快:

    public static InternalThreadLocalMap get() {
        Thread thread = Thread.currentThread();
        if (thread instanceof FastThreadLocalThread) {
            return fastGet((FastThreadLocalThread) thread);
        } else {
            return slowGet();
        }
    }

從get方法可以看到,如果當(dāng)前thread是FastThreadLocalThread的話,則會(huì)去調(diào)用fastGet方法,否則調(diào)用slowGet方法。

slowGet方法就是使用傳統(tǒng)的ThreadLocal來(lái)get:

    private static InternalThreadLocalMap slowGet() {
        InternalThreadLocalMap ret = slowThreadLocalMap.get();
        if (ret == null) {
            ret = new InternalThreadLocalMap();
            slowThreadLocalMap.set(ret);
        }
        return ret;
    }

我們重點(diǎn)關(guān)注下fastGet方法:

    private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
        InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
        if (threadLocalMap == null) {
            thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
        }
        return threadLocalMap;
    }

這里fast的效果就出現(xiàn)了,fastGet直接返回了thread中的InternalThreadLocalMap對(duì)象,不需要進(jìn)行任何查找的過(guò)程。

再看下FastThreadLocal如何使用get方法來(lái)獲取具體的值:

    public final V get() {
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
        Object v = threadLocalMap.indexedVariable(index);
        if (v != InternalThreadLocalMap.UNSET) {
            return (V) v;
        }

        return initialize(threadLocalMap);
    }

可以看到FastThreadLocal中的get首先調(diào)用了InternalThreadLocalMap的get方法,直接返回了FastThreadLocalThread中的InternalThreadLocalMap對(duì)象,這個(gè)速度是非??斓?。

然后直接使用FastThreadLocal中的index,來(lái)獲取threadLocalMap中具體存儲(chǔ)數(shù)據(jù)的數(shù)組中的元素:

    public Object indexedVariable(int index) {
        Object[] lookup = indexedVariables;
        return index < lookup.length? lookup[index] : UNSET;
    }

因?yàn)槭侵苯觟ndex訪問(wèn)的,所以也非??臁_@就是fast的由來(lái)。

那么有同學(xué)會(huì)問(wèn)題了,F(xiàn)astThreadLocal中的index是怎么來(lái)的呢?

    private final int index;

    public FastThreadLocal() {
        index = InternalThreadLocalMap.nextVariableIndex();
    }

而InternalThreadLocalMap中的nextVariableIndex方法是一個(gè)靜態(tài)方法:

    public static int nextVariableIndex() {
        int index = nextIndex.getAndIncrement();
        if (index < 0) {
            nextIndex.decrementAndGet();
            throw new IllegalStateException("too many thread-local indexed variables");
        }
        return index;
    }

也就是說(shuō),只要new一個(gè)FastThreadLocal,該對(duì)象中,就會(huì)生成一個(gè)唯一的index。然后FastThreadLocal使用該index去InternalThreadLocalMap中存取對(duì)象。這樣就不存在ThreadLocal那種需要多次遍歷查找的情況。

總結(jié)

FastThreadLocal是和FastThreadLocalThread配套使用才會(huì)真正的fast,否則的話就會(huì)fallback到ThreadLocal去執(zhí)行,大家一定要注意這一點(diǎn)。

更多內(nèi)容請(qǐng)參考 http://www.flydean.com/48-netty-fastthreadlocal/

最通俗的解讀,最深刻的干貨,最簡(jiǎn)潔的教程,眾多你不知道的小技巧等你來(lái)發(fā)現(xiàn)!

歡迎關(guān)注我的公眾號(hào):「程序那些事」,懂技術(shù),更懂你!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容