1. 程式人生 > >Netty原始碼分析第8章(高效能工具類FastThreadLocal和Recycler)---->第1節: FastThreadLocal的使用和建立

Netty原始碼分析第8章(高效能工具類FastThreadLocal和Recycler)---->第1節: FastThreadLocal的使用和建立

 

Netty原始碼分析第八章: 高效能工具類FastThreadLocal和Recycler

 

概述:

 

        FastThreadLocal我們在剖析堆外記憶體分配的時候簡單介紹過, 它類似於JDK的ThreadLocal, 也是用於在多執行緒條件下, 保證統一執行緒的物件共享, 只是netty中定義的FastThreadLocal, 效能要高於jdk的ThreadLocal, 具體原因會在之後的小節進行剖析

 

        Recyler我們應該也不會太陌生, 因為在之前章節中, 有好多地方使用了Recyler

 

        Recyler是netty實現的一個輕量級物件回收站, 很多物件在使用完畢之後, 並沒有直接交給gc去處理, 而是通過物件回收站將物件回收, 目的是為了物件重用和減少gc壓力

 

        比如ByteBuf物件的回收, 因為ByteBuf物件在netty中會頻繁建立, 並且會佔用比較大的記憶體空間, 所以使用完畢後會通過物件回收站的方式進行回收, 已達到資源重用的目的

 

        這一章就對FastThreadLocal和Recyler兩個併發工具類進行分析

 

第一節:FastThreadLocal的使用和建立

 

 

首先我們看一個最簡單的demo:

public class FastThreadLocalDemo {

    final class FastThreadLocalTest extends FastThreadLocal<Object>{
        @Override
        protected Object initialValue() throws Exception {
            return new Object();
        }
    }

    
private final FastThreadLocalTest fastThreadLocalTest; public FastThreadLocalDemo(){ fastThreadLocalTest = new FastThreadLocalTest(); } public static void main(String[] args){ FastThreadLocalDemo fastThreadLocalDemo = new FastThreadLocalDemo(); new Thread(new Runnable() { @Override public void run() { Object obj = fastThreadLocalDemo.fastThreadLocalTest.get(); try { for (int i=0;i<10;i++){ fastThreadLocalDemo.fastThreadLocalTest.set(new Object()); Thread.sleep(1000); } }catch (Exception e){ e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { Object obj = fastThreadLocalDemo.fastThreadLocalTest.get(); for (int i=0;i<10;i++){ System.out.println(obj == fastThreadLocalDemo.fastThreadLocalTest.get()); Thread.sleep(1000); } }catch (Exception e){ } } }).start(); } }

這裡首先宣告一個內部類FastThreadLocalTest繼承FastThreadLocal, 並重寫initialValue方法, initialValue方法就是用來初始化執行緒共享物件的

然後宣告一個成員變數fastThreadLocalTest, 型別就是內部類FastThreadLocalTest

在構造方法中初始化fastThreadLocalTest

 

main方法中建立當前類FastThreadLocalDemo的物件fastThreadLocalDemo

然後啟動兩個執行緒, 每個執行緒通過fastThreadLocalDemo.fastThreadLocalTest.get()的方式拿到執行緒共享物件, 因為fastThreadLocalDemo是相同的, 所以fastThreadLocalTest物件也是同一個, 同一個物件在不同執行緒中進行get()

第一個執行緒迴圈通過set方法修改共享物件的值

第二個執行緒則迴圈判斷並輸出fastThreadLocalTest.get()出來的物件和第一次get出來的物件是否相等

這裡輸出結果都true, 說明其他執行緒雖然不斷修改共享物件的值, 但都不影響當前執行緒共享物件的值

這樣就實現了執行緒共享的物件的功能

 

根據上述示例, 我們剖析FastThreadLocal的建立

首先跟到FastThreadLocal的構造方法中:

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

這裡的index, 代表FastThreadLocal物件的一個下標, 每建立一個FastThreadLocal, 都會有一個唯一的自增的下標

跟到nextVariableIndex方法中:

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

這裡只是獲取nextIndex通過getAndIncrement()進行原子自增, 建立第一個FastThreadLocal物件時, nextIndex為0, 建立第二個FastThreadLocal物件時nextIndex為1, 以此類推, 第n次nextIndex為n-1, 如圖所示

8-1-1

我們回到demo中, 我們看執行緒中的這一句:

Object obj = fastThreadLocalDemo.fastThreadLocalTest.get();

這裡呼叫了FastThreadLocal物件的get方法, 作用是建立一個執行緒共享物件

我們跟到get方法中:

public final V get() {
    return get(InternalThreadLocalMap.get());
}

這裡呼叫了一個過載的get方法, 引數中通過InternalThreadLocalMap的get方法獲取了一個InternalThreadLocalMap物件

我們跟到InternalThreadLocalMap的get方法中, 分析其實如何獲取InternalThreadLocalMap物件的

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

這裡首先拿到當前執行緒, 然後判斷當前執行緒是否為FastThreadLocalThread執行緒, 通常NioEventLoop執行緒都是FastThreadLocalThread, 用於執行緒則不是FastThreadLocalThread

在這裡, 如果FastThreadLocalThread執行緒, 則呼叫fastGet方法獲取InternalThreadLocalMap, 從名字上我們能知道, 這是一種效率極高的獲取方式

如果不是FastThreadLocalThread執行緒, 則呼叫slowGet方式獲取InternalThreadLocalMap, 同樣根據名字, 我們知道這是一種效率不太高的獲取方式

我們的demo並不是eventLoop執行緒, 所以這裡會走到slowGet()方法中

我們首先剖析slowGet()方法:

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

首先通過UnpaddedInternalThreadLocalMap.slowThreadLocalMap拿到一個ThreadLocal物件slowThreadLocalMap, slowThreadLocalMap是UnpaddedInternalThreadLocalMap類的一個靜態屬性, 型別是ThreadLocal型別

這裡的ThreadLocal是jdk的ThreadLocal

然後通過slowThreadLocalMap物件的get方法, 獲取一個InternalThreadLocalMap

如果第一次獲取, InternalThreadLocalMap有可能是null, 所以在if塊中, new了一個InternalThreadLocalMap物件, 並設定在ThreadLocal物件中

因為netty實現的FastThreadLocal要比jdk的ThreadLocal要快, 所以這裡的方法叫slowGet

回到InternalThreadLocalMap的get方法:

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

我們繼續剖析fastGet方法, 通常EventLoop執行緒FastThreadLocalThread執行緒, 所以EventLoop執行緒執行到這一步的時候會呼叫fastGet方法

我們跟進fastGet:

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

首先FastThreadLocalThread物件直接通過threadLocalMap拿到threadLocalMap物件

如果threadLocalMap為null, 則建立一個InternalThreadLocalMap物件設定到FastThreadLocalThread的成員變數中

這裡我們可以知道FastThreadLocalThread物件中維護了一個InternalThreadLocalMap型別的成員變數, 可以直接通過threadLocalMap()方法獲取該變數的值, 也就是InternalThreadLocalMap

我們跟到InternalThreadLocalMap的構造方法中:

private InternalThreadLocalMap() {
    super(newIndexedVariableTable());
}

這裡呼叫了父類的構造方法, 傳入一個newIndexedVariableTable()

我們跟到newIndexedVariableTable()中:

private static Object[] newIndexedVariableTable() {
    Object[] array = new Object[32];
    Arrays.fill(array, UNSET);
    return array;
}

這裡建立一個長度為32的陣列, 併為陣列中的每一個物件設定為UNSET, UNSET是一個Object的物件, 表示該下標的值沒有被設定

回到InternalThreadLocalMap的構造方法, 再看其父類的構造方法:

UnpaddedInternalThreadLocalMap(Object[] indexedVariables) {
    this.indexedVariables = indexedVariables;
}

這裡初始化了一個數組型別的成員變數indexedVariables, 就是newIndexedVariableTable返回object的陣列

這裡我們可以知道, 每個InternalThreadLocalMap物件中都維護了一個Object型別的陣列, 那麼這個陣列有什麼作用呢?我們繼續往下剖析

回到FastThreadLocal的get方法中:

public final V get() {
    return get(InternalThreadLocalMap.get());
}

我們剖析完了InternalThreadLocalMap.get()的相關邏輯, 再繼續看過載的get方法:

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

首先看這一步:

Object v = threadLocalMap.indexedVariable(index);

這一步是拿到當前index下標的object, 其實也就是拿到每個FastThreadLocal物件的繫結的執行緒共享物件

index是我們剛才分析過, 是每一個FastThreadLocal的唯一下標

我們跟到indexedVariable方法中:

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

這裡首先拿到indexedVariables, 我們剛才分析過, indexedVariables是InternalThreadLocalMap物件中維護的陣列, 初始大小是32

然後再return中判斷當前index是不是小於當前陣列的長度, 如果小於則獲取當前下標index的陣列元素, 否則返回UNSET代表沒有設定的物件

這裡我們可以分析到, 其實每一個FastThreadLocal物件中所繫結的執行緒共享物件, 是存放在threadLocalMap物件中的一個物件陣列的中的, 陣列中的元素的下標其實就是對應著FastThreadLocal中的index屬性, 對應關係如圖所示

8-1-2

回到FastThreadLocal過載的get方法:

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

根據以上邏輯, 我們知道, 第一次獲取物件v是隻能獲取到UNSET物件, 因為該物件並沒有儲存在threadLocalMap中的陣列indexedVariables中, 所以第一次獲取在if判斷中為false, 會走到initialize方法中

跟到initialize方法中:

private V initialize(InternalThreadLocalMap threadLocalMap) {
    V v = null;
    try {
        v = initialValue();
    } catch (Exception e) {
        PlatformDependent.throwException(e);
    }
    threadLocalMap.setIndexedVariable(index, v);
    addToVariablesToRemove(threadLocalMap, this);
    return v;
}

這裡首先呼叫的initialValue方法, 這裡的initialValue實際上走的是FastThreadLocal子類的重寫initialValue方法

在我們的demo中對應這個方法:

@Override
protected Object initialValue() throws Exception {
    return new Object();
}

通過這個方法會建立一個執行緒共享物件

然後通過threadLocalMap物件的setIndexedVariable方法將建立的執行緒共享物件設定到threadLocalMap中維護的陣列中, 引數為FastThreadLocal和建立的物件本身

跟到setIndexedVariable方法中:

public boolean setIndexedVariable(int index, Object value) {
    Object[] lookup = indexedVariables;
    if (index < lookup.length) {
        Object oldValue = lookup[index];
        lookup[index] = value;
        return oldValue == UNSET;
    } else {
        expandIndexedVariableTableAndSet(index, value);
        return true;
    }
}

這裡首先判斷FastThreadLocal物件的index是否超過陣列indexedVariables的長度, 如果沒有超過, 則直接通過下標設定新建立的執行緒共享物件, 通過這個操作, 下次獲取該物件的時候就可以直接通過陣列下標進行取出

如果index超過了陣列indexedVariables的長度, 則通過expandIndexedVariableTableAndSet方法將陣列擴容, 並且根據index的通過陣列下標的方式將執行緒共享物件設定到陣列indexedVariables中

以上就是執行緒共享物件的建立和獲取的過程