追蹤解析 Netty 的 FastThreadLocal 源碼

零 前期準備

0 FBI WARNING

文章異常囉嗦且繞彎。java

1 版本

JDK 版本 : OpenJDK 11.0.1數組

IDE : idea 2018.3ide

Netty 版本 : netty-all 4.1.34.Final工具

2 FastThreadLocal 簡介

FastThreadLocal 是 Netty 中實現的高性能 ThreadLocal 工具,功能上和 ThreadLocal 差很少,可是性能上遠高於 jdk 自帶的 ThreadLocal。性能

3 Demo

import io.netty.util.concurrent.FastThreadLocal;

public class FastThreadLocalDemo {

    public static void main(String[] args) {

        //建立 FastThreadLocal 對象
        FastThreadLocal<String> tl = new FastThreadLocal<>();

        //FastThreadLocal 的存入功能
        long setBegin = System.nanoTime();
        tl.set("test");
        long setAfter = System.nanoTime();
        System.out.println("get : " + (setAfter - setBegin));

        //FastThreadLocal 的獲取功能
        long getBegin = System.nanoTime();
        String fastGet = tl.get();
        long getAfter = System.nanoTime();
        System.out.println("get : " + (getAfter - getBegin));

        //FastThreadLocal 的移除功能
        long removeBegin = System.nanoTime();
        tl.remove();
        long removeAfter = System.nanoTime();
        System.out.println("remove : " + (removeAfter - removeBegin));
    }
}

一 FastThreadLocal 的建立

回到 Demo 中的建立代碼:學習

FastThreadLocal<String> tl = new FastThreadLocal<>();

追蹤 FastThreadLocal 的構造器:this

//step 1
//FastThreadLocal.class
public FastThreadLocal() {
    //index 是一個 int 類型的變量
    index = InternalThreadLocalMap.nextVariableIndex();
}

//step 2
//InternalThreadLocalMap.class
public static int nextVariableIndex() {
    //nextIndex 是一個定義在 UnpaddedInternalThreadLocalMap 類中的靜態 AtomicInteger 類型對象
    //UnpaddedInternalThreadLocalMap 是 InternalThreadLocalMap 的父類
    //這裏使用自增操做獲取一個 int 值,並返回
    int index = nextIndex.getAndIncrement();
    if (index < 0) {
        nextIndex.decrementAndGet();
        throw new IllegalStateException("too many thread-local indexed variables");
    }
    return index;
}

其實 FastThreadLocal 的建立就只是獲取一個惟一的 int 值做爲標識,沒有其它操做了。idea

二 InternalThreadLocalMap

InternalThreadLocalMap 是 FastThreadLocal 底層真正起做用的 ThreadLocal 類,而且提供了大量的靜態方法。.net

1 獲取對象

最爲核心的是 InternalThreadLocalMap 的 get() 方法:線程

//InternalThreadLocalMap.class
public static InternalThreadLocalMap get() {
    //獲取當前線程的線程對象
    Thread thread = Thread.currentThread();

    //判斷線程對象的類型
    if (thread instanceof FastThreadLocalThread) {
        //在 Netty 中使用的 FastThreadLocal 的時候會用到該類型的方法
        return fastGet((FastThreadLocalThread) thread);
    } else {
        //正常狀況下單獨使用 FastThreadLocal,線程對象不會是 FastThreadLocalThread
        return slowGet();
    }
}

先來看 fastGet():

//InternalThreadLocalMap.class
private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
    //直接獲取 FastThreadLocalThread 中的 threadLocalMap 對象並返回便可
    InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
    
    //爲空的話新建一個
    if (threadLocalMap == null) {
        thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
    }
    return threadLocalMap;
}

再來看 slowGet():

//InternalThreadLocalMap.class
private static InternalThreadLocalMap slowGet() {
    //獲取 UnpaddedInternalThreadLocalMap 中的 slowThreadLocalMap 對象
    //slowThreadLocalMap 是一個靜態的 ThreadLocal 類型對象,儲存的數據類型是 InternalThreadLocalMap
    ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap;

    //獲取該對象中的 InternalThreadLocalMap 對象實例並返回
    InternalThreadLocalMap ret = slowThreadLocalMap.get();

    //爲空的狀況下新建一個
    if (ret == null) {
        ret = new InternalThreadLocalMap();
        slowThreadLocalMap.set(ret);
    }
    return ret;
}

能夠看到實際上對於 FastThreadLocal 來講,真正起做用的是 InternalThreadLocalMap 對象。

在普通的線程中,這個對象因爲自己沒有 jdk 的原生支持,因此只能附着在 ThreadLocal 對象當中。

可是因爲 UnpaddedInternalThreadLocalMap 中的 slowThreadLocalMap 自己是一個靜態的 ThreadLocal 對象,因此不一樣的線程實際上調用到的都是同一個對象,可是獲取到的 InternalThreadLocalMap 卻不是同一個。同一個線程中若是建立多個 FastThreadLocal 對象,獲取到的是同一個 InternalThreadLocalMap。

2 set

InternalThreadLocalMap 的底層儲存是一個 Object 數組,經過 setIndexedVariable(...) 方法儲存進去:

//InternalThreadLocalMap.class
public boolean setIndexedVariable(int index, Object value) {
    //indexedVariables 是一個定義在 UnpaddedInternalThreadLocalMap 中的 Object 數組
    Object[] lookup = indexedVariables;

    //在這裏須要判斷數組的長度問題
    //index 是每一個 FastThreadLocal 建立的時候都會獲取的惟一標識碼,同時也是數組上的位置
    if (index < lookup.length) {
        //獲取原值
        Object oldValue = lookup[index];
        //賦值
        lookup[index] = value;
        //UNSET 是一個靜態的 Object 對象,用於默認填充 lookup 數組
        //此處 oldValue 若是等於 UNSET,則證實該位置上原來不存在對象儲存
        //若是是已經儲存過對象,又調用該方法替換了一次,會返回 false
        return oldValue == UNSET;
    } else {
        //數組擴容
        expandIndexedVariableTableAndSet(index, value);
        return true;
    }
}

3 get

InternalThreadLocalMap 中獲取值的方法是經過 indexedVariable(...) 方法:

//InternalThreadLocalMap.class
public Object indexedVariable(int index) {
    //根據 index 從數組中獲取到想要的位置的值
    Object[] lookup = indexedVariables;
    return index < lookup.length? lookup[index] : UNSET;
}

4 remove

InternalThreadLocalMap 中刪除值的方法是經過 indexedVariable(...) 方法:

//InternalThreadLocalMap.class
public Object removeIndexedVariable(int index) {
    //獲取數組
    Object[] lookup = indexedVariables;
    if (index < lookup.length) {
        Object v = lookup[index];
        //將指定位置的值替換成 UNSET 對象
        lookup[index] = UNSET;
        return v;
    } else {
        return UNSET;
    }
}

InternalThreadLocalMap 還有一個靜態的 remove() 方法用於清除自身:

//InternalThreadLocalMap.class
public static void remove() {
    Thread thread = Thread.currentThread();
    if (thread instanceof FastThreadLocalThread) {
        ((FastThreadLocalThread) thread).setThreadLocalMap(null);
    } else {
        slowThreadLocalMap.remove();
    }
}

代碼比較簡單,不作過多分析。

二 存入元素

回到 Demo 中的存入元素的代碼:

tl.set("test");

追蹤 get(...) 方法:

//step 1
//FastThreadLocal.class
public final void set(V value) {
    //先判斷 value 不是 UNSET 對象
    if (value != InternalThreadLocalMap.UNSET) {
        //獲取 InternalThreadLocalMap 對象
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();

        //setKnownNotUnset(...) 方法會將 value 存入 threadLocalMap 中
        if (setKnownNotUnset(threadLocalMap, value)) {
            //此處會清理已經被 gc 回收的線程對象所儲存的值
            registerCleaner(threadLocalMap);
        }
    } else {
        remove();
    }
}

//step 2
//FastThreadLocal.class
private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
    //調用 setIndexedVariable(...) 方法去存儲 value,具體見上方 InternalThreadLocalMap 的詳細解讀
    if (threadLocalMap.setIndexedVariable(index, value)) {
        //addToVariablesToRemove(...) 方法會將 FastThreadLocal 對象存放到 threadLocalMap 中的一個集合中
        //這個集合用於在須要的時候集中銷燬 FastThreadLocal
        addToVariablesToRemove(threadLocalMap, this);
        return true;
    }
    return false;
}

三 獲取元素

回到 Demo 中獲取元素的代碼:

String fastGet = tl.get();

追蹤 set(...) 方法:

//FastThreadLocal.class
public final V get() {
    //獲取 InternalThreadLocalMap 對象
    InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
    //從 InternalThreadLocalMap 中獲取值
    Object v = threadLocalMap.indexedVariable(index);

    //若是存在值,則直接返回該值便可
    if (v != InternalThreadLocalMap.UNSET) {
        return (V) v;
    }

    //不存在的狀況下,initialize(...) 會返回一個 null 值
    V value = initialize(threadLocalMap);

    //gc 處理
    registerCleaner(threadLocalMap);

    //返回 null
    return value;
}

三 移除元素

回到 Demo 中移除元素的代碼:

String fastGet = tl.get();

追蹤 remove(...) 方法:

//step 1
//FastThreadLocal.class
public final void remove() {
    //InternalThreadLocalMap 的 getIfSet() 方法會獲取 InternalThreadLocalMap 對象
    remove(InternalThreadLocalMap.getIfSet());
}

//step 2
//FastThreadLocal.class
public final void remove(InternalThreadLocalMap threadLocalMap) {

    //有效性驗證
    if (threadLocalMap == null) {
        return;
    }

    //清除值
    Object v = threadLocalMap.removeIndexedVariable(index);

    //到以前 threadLocalMap 中保存 FastThreadLocal 對象的集合裏去刪除對象
    removeFromVariablesToRemove(threadLocalMap, this);

    if (v != InternalThreadLocalMap.UNSET) {
        try {
            //此方法爲空,是預留的一個處理方法,使用者也能夠本身作實現
            onRemoval((V) v);
        } catch (Exception e) {
            PlatformDependent.throwException(e);
        }
    }
}

四 內存管理

在真實的開發環境中,可能會存在一個線程使用了此 FastThreadLocal,而後線程完成以後被 gc 回收了,可是該 FastThreadLocal 的值沒有被回收的狀況。

因此在 FastThreadLocal 中就由一個防止內存泄漏的方法 registerCleaner(...):

//FastThreadLocal.class
private void registerCleaner(final InternalThreadLocalMap threadLocalMap) {

    Thread current = Thread.currentThread();

    //若是 FastThreadLocalThread 被標記爲要被清理,或者 index 這個位置的元素並不被收錄於清理目錄下,則直接返回
    if (FastThreadLocalThread.willCleanupFastThreadLocals(current) || threadLocalMap.isCleanerFlagSet(index)) {
        return;
    }

    //將 index 收錄到清理目錄中
    threadLocalMap.setCleanerFlag(index);

    //下方代碼是防止內存泄漏的核心代碼,可是已經被註釋掉了
    //值得一提的是,在之前的 Netty 版本中是存在的,可是在筆者追蹤的 4.1.34 版本中被註釋掉了
    //根據解釋,是官方以爲這種處理方式不夠優雅,因此暫時將此段代碼註釋掉了,而且打上了 TODO 字樣

//    ObjectCleaner.register(current, new Runnable() {
//        @Override
//        public void run() {
//            remove(threadLocalMap);
//        }
//   });
}

五 一點嘮叨

FastThreadLocal 對比 jdk 的原生 ThreadLocal,性能優點主要表如今如下幾個方面:

一、Netty 基於本身的業務需求,對線程對象進行了封裝,並在此過程當中內嵌了對 FastThreadLocal 的支持
二、FastThreadLocal 中省略了 ThreadLocal 中的節點對象的組裝和 Hash 值的計算過程,結構更加簡單,存、拿過程的效率更高
三、ThreadLocal 對於內存的控制比 FastThreadLocal 更加嚴謹,消耗更多的精力去進行內存檢查和清理
四、FastThreadLocal 中靜態(static)方法的使用更加頻繁,是典型的以空間換時間的作法

本文僅爲我的的學習筆記,可能存在錯誤或者表述不清的地方,有緣補充

相關文章
相關標籤/搜索