FastThreadLocal 是什麼鬼?吊打 ThreadLocal 的存在!!

Java技術棧發表於2020-11-04

ThreadLocal 大家都知道是執行緒本地變數,今天棧長再介紹一個神器:FastThreadLocal,從字面上看就是:Fast + ThreadLocal,一個快的 ThreadLocal?這到底是什麼鬼呢?

一、FastThreadLocal 簡介

FastThreadLocal 並不是 JDK 自帶的,而是在 Netty 中造的一個輪子,Netty 為什麼要重複造輪子呢?

來看下它原始碼中的註釋定義:

/**
 * A special variant of {@link ThreadLocal} that yields higher access performance when accessed from a
 * {@link FastThreadLocalThread}.
 * <p>
 * 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.
 * </p><p>
 * To take advantage of this thread-local variable, your thread must be a {@link FastThreadLocalThread} or its subtype.
 * By default, all threads created by {@link DefaultThreadFactory} are {@link FastThreadLocalThread} due to this reason.
 * </p><p>
 * Note that the fast path is only possible on threads that extend {@link FastThreadLocalThread}, because it requires
 * a special field to store the necessary state.  An access by any other kind of thread falls back to a regular
 * {@link ThreadLocal}.
 * </p>
 *
 * @param <V> the type of the thread-local variable
 * @see ThreadLocal
 */
public class FastThreadLocal<V> {
	...
}

FastThreadLocal 是一個特殊的 ThreadLocal 變體,當從執行緒類 FastThreadLocalThread 中訪問 FastThreadLocalm時可以獲得更高的訪問效能。如果你還不知道什麼是 ThreadLocal,可以關注公眾號Java技術棧閱讀我之前分享的文章。

二、FastThreadLocal 為什麼快?

在 FastThreadLocal 內部,使用了索引常量代替了 Hash Code 和雜湊表,原始碼如下:

private final int index;

public FastThreadLocal() {
    index = InternalThreadLocalMap.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;
}

FastThreadLocal 內部維護了一個索引常量 index,該常量在每次建立 FastThreadLocal 中都會自動+1,從而保證了下標的不重複性。

這要做雖然會產生大量的 index,但避免了在 ThreadLocal 中計算索引下標位置以及處理 hash 衝突帶來的損耗,所以在運算元組時使用固定下標要比使用計算雜湊下標有一定的效能優勢,特別是在頻繁使用時會非常顯著,用空間換時間,這就是高效能 Netty 的巧妙之處。

要利用 FastThreadLocal 帶來的效能優勢,就必須結合使用 FastThreadLocalThread 執行緒類或其子類,因為 FastThreadLocalThread 執行緒類會儲存必要的狀態,如果使用了非 FastThreadLocalThread 執行緒類則會回到常規 ThreadLocal。

Netty 提供了繼承類和實現介面的執行緒類:

  • FastThreadLocalRunnable
  • FastThreadLocalThread

Netty 也提供了 DefaultThreadFactory 工廠類,所有由 DefaultThreadFactory 工廠類建立的執行緒預設就是 FastThreadLocalThread 型別,來看下它的建立過程:

先建立 FastThreadLocalRunnable,再建立 FastThreadLocalThread,基友搭配,幹活不累,一定要配合使用才“快”。

三、FastThreadLocal 實戰

要使用 FastThreadLocal 就需要匯入 Netty 的依賴了:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.52.Final</version>
</dependency>

寫一個測試小示例:

import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.FastThreadLocal;

public class FastThreadLocalTest {

    public static final int MAX = 100000;

    public static void main(String[] args) {
        new Thread(() -> threadLocal()).start();
        new Thread(() -> fastThreadLocal()).start();
    }

    private static void fastThreadLocal() {
        long start = System.currentTimeMillis();
        DefaultThreadFactory defaultThreadFactory = new DefaultThreadFactory(FastThreadLocalTest.class);

        FastThreadLocal<String>[] fastThreadLocal = new FastThreadLocal[MAX];

        for (int i = 0; i < MAX; i++) {
            fastThreadLocal[i] = new FastThreadLocal<>();
        }

        Thread thread = defaultThreadFactory.newThread(() -> {
            for (int i = 0; i < MAX; i++) {
                fastThreadLocal[i].set("java: " + i);
            }

            System.out.println("fastThreadLocal set: " + (System.currentTimeMillis() - start));

            for (int i = 0; i < MAX; i++) {
                for (int j = 0; j < MAX; j++) {
                    fastThreadLocal[i].get();
                }
            }
        });
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("fastThreadLocal total: " + (System.currentTimeMillis() - start));
    }

    private static void threadLocal() {
        long start = System.currentTimeMillis();
        ThreadLocal<String>[] threadLocals = new ThreadLocal[MAX];

        for (int i = 0; i < MAX; i++) {
            threadLocals[i] = new ThreadLocal<>();
        }

        Thread thread = new Thread(() -> {
            for (int i = 0; i < MAX; i++) {
                threadLocals[i].set("java: " + i);
            }

            System.out.println("threadLocal set: " + (System.currentTimeMillis() - start));

            for (int i = 0; i < MAX; i++) {
                for (int j = 0; j < MAX; j++) {
                    threadLocals[i].get();
                }
            }
        });
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("threadLocal total: " + (System.currentTimeMillis() - start));
    }

}

結果輸出:

可以看出,在大量讀寫面前,寫操作的效率差不多,但讀操作 FastThreadLocal 比 ThreadLocal 快的不是一個數量級,簡直是秒殺 ThreadLocal 的存在。

當我把 MAX 值調整到 1000 時,結果輸出:

讀寫操作不多時,ThreadLocal 明顯更勝一籌!

上面的示例是單執行緒測試多個 *ThreadLocal,即陣列形式,另外,我也測試了多執行緒單個 *ThreadLocal,這時候 FastThreadLocal 效率就明顯要落後於 ThreadLocal。。

最後需要說明的是,在使用完 FastThreadLocal 之後不用 remove 了,因為在 FastThreadLocalRunnable 中已經加了移除邏輯,線上程執行完時會移除全部繫結在當前執行緒上的所有變數。

所以,使用 FastThreadLocal 導致記憶體溢位的概率會不會要低於 ThreadLocal?

不一定,因為 FastThreadLocal 會產生大量的 index 常量,所謂的空間換時間,所以感覺 FastThreadLocal 記憶體溢位的概率更大,但好在每次使用完都會自動 remove。

四、總結

Netty 中的 FastThreadLocal 在大量頻繁讀寫操作時效率要高於 ThreadLocal,但要注意結合 Netty 自帶的執行緒類使用,這可能就是 Netty 為什麼高效能的奧妙之一吧!

如果沒有大量頻繁讀寫操作的場景,JDK 自帶的 ThreadLocal 足矣,並且效能還要優於 FastThreadLocal。

好了,今天的分享就到這裡了,覺得有用,轉發分享一下哦。

最後,Java 系列教程還會繼續更新,關注Java技術棧公眾號第一時間推送,還可以在公眾號選單中獲取歷史 Java 教程,都是乾貨。

版權申明:本文系公眾號 "Java技術棧" 原創,原創實屬不易,轉載、引用本文內容請註明出處,禁止抄襲、洗稿,請自重,尊重他人勞動成果和智慧財產權。

近期熱文推薦:

1.Java 15 正式釋出, 14 個新特性,重新整理你的認知!!

2.終於靠開源專案弄到 IntelliJ IDEA 啟用碼了,真香!

3.我用 Java 8 寫了一段邏輯,同事直呼看不懂,你試試看。。

4.吊打 Tomcat ,Undertow 效能很炸!!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!

相關文章