【源起Netty 外傳】FastThreadLocal怎麼Fast?

概述

FastThreadLocal的類名自己就充滿了對ThreadLocal的挑釁,「快男」FastThreadLocal是怎麼快的?源碼中類註釋坦白以下:html

/**
 * ...
 * Internally, a {@link FastThreadLocal} uses a constant index in an array, instead of using hash code and hash table,
 * to look for a variable.  Although seemingly very subtle, it yields slight performance advantage over using a hash
 * table, and it is useful when accessed frequently.
 * ...
 */

大概意思就是:用索引代替了ThreadLocal中的threadLocalHashCode,當請求頻繁時,這個小改動就會顯現其效果。
提到FastThreadLocal,就不得不提它的好基友FastThreadLocalThread,簡單來講,FastThreadLocal就是爲FastThreadLocalThread量身打造的!java

FastThreadLocalThread又是哪一個單位的?且聽我慢慢道來……數組

FastThreadLocalThread

通常來講,Netty的client端,是這麼建立的:併發

EventLoopGroup group = new NioEventLoopGroup();

沿着調用鏈,層層深刻NioEventLoopGroup的構造函數,在MultithreadEventExecutorGroup構造塊,會看到這樣的邏輯:函數

if (executor == null) {
    executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}

newDefaultThreadFactory()方法,功能如其名,建立了DefaultThreadFactory工廠:oop

protected ThreadFactory newDefaultThreadFactory() {
    return new DefaultThreadFactory(getClass());
}

DefaultThreadFactory工廠的產品,就是FastThreadLocalThreadthis

ok,FastThreadLocalThread物種起源的事兒暫且放下,咱們來簡單過一下原版ThreadLocal的工做機制。spa

ThreadLocal

ThreadLocal<T>你們應該並不陌生,兩個最核心API線程

//賦值方法
public void set(T value);

//取值方法
public T get();

用以併發環境下,線程生命週期內的存取數據操做,隔離其它線程干擾。code

實現原理淺談

幫助理解的示意圖:
clipboard.png

Thread中有一屬性threadLocalsThreadLocal.ThreadLocalMap類型(ThreadLocalMap是ThreadLocal的靜態內部類)。ThreadLocal的set(T value)方法被調用時,會將參數value存放於當前線程的threadLocals中,想要獲取時,再從threadLocals中獲取。

剛剛說過,threadLocals是一個ThreadLocalMap(ThreadLocal中的靜態內部類),Entry則是ThreadLocalMap的內部節點。而ThreadLocalMap做爲一個Map,人設是這樣的ThreadLocalMap<ThreadLocal<T>>,T>,即key=ThreadLocal<T>value=T

  • 存入 set(T value)
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);    //根據當前線程獲取ThreadLocalMap
    if (map != null)
        map.set(this, value);    //map的key直接使用的當前ThreadLocal對象
    else
        createMap(t, value);
}

線程向ThreadLocal存入時,第一次調用將在線程中分配一塊空間,初始大小爲Entry[16]數組。而後,以做爲key的ThreadLocal<T>計算出hashCode,稍加計算得出Entry[16]數組的索引i。最後,爲槽位Entry[i]賦值上value。

  • 獲取 T get()
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);    //找到與當前線程綁定的ThreadLocalMap
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

獲取時,先找到與當前線程綁定的ThreadLocalMap,而後你懂得……

聽上去一副滿滿的HashMap的套路,其實內在有很大差異,好比ThreadLocal的Entry實現了WeakReference弱引用)。

一個小問題

ThreadLocal爲何用Entry數組做爲內置實現?並且初始就16位,還有擴容等實現。對某一線程而言,ThreadLocal不是隻能存儲一個值嗎?這麼想的話,用單個Object就能存儲了……

這個萬惡的問題,當時困擾了我很長時間,其實想通後很簡單:
Entry數組爲了實現ThreadLocalMap,而ThreadLocalMap君的key是ThreadLocal,多個ThreadLocal均可以存儲在ThreadLocalMap中。沒錯,A線程只用一個ThreadLocal的話,確實用一個Object做爲內置實現就搞定了,可是A線程使用多個ThreadLocal時,沒法知足!

FastThreadLocal

ThreadLocal就分析到這裏,讓咱們迴歸FastThreadLocal。

結構及關鍵方法示意圖:

clipboard.png

前文提到過,FastThreadLocal的關鍵是indexindex能夠理解成一個FastThreadLocal對象的惟一ID。這個index會在FastThreadLocalThread線程中,做爲其indexedVariables屬性(初始是一個Object[32]數組)的索引,達成與ThreadLocal相似的效果。

值得一提的是Object[] indexedVariables的第0位(Object[0]),用於存放將來會被釋放的數據,其實存儲的就是以前FastThreadLocal的set方法操做過的槽位信息。FastThreadLocal的remove方法被調用時,會根據記錄的槽位信息進行大掃除。

FastThreadLocal相關內容就聊到這裏,更多細節請翻看源碼!

感謝

ThreadLocal源碼解讀

相關文章
相關標籤/搜索