14.Netty的FastThreadLocal

总结
  • FastThreadLocal 用数组下标直接定位,O(1) 查找,比 JDK ThreadLocal 的哈希表更快
  • 扩容也更简单,不需要 rehash,直接按 2 的幂次扩容拷贝
  • 内存安全性更高,配合 FastThreadLocalRunnable 可以在线程池场景下自动清理
  • 底层依赖 InternalThreadLocalMap,每个 FastThreadLocal 实例在创建时就分配好了固定的数组下标

1. 为什么要有 FastThreadLocal?

JDK 的 ThreadLocal 用起来没什么大问题,但在 Netty 这种高并发、高频率创建线程局部变量的场景下,它的性能瓶颈就暴露出来了:

FastThreadLocal 就是 Netty 针对这些问题做的优化版本。

2. 核心优势

2.1 查找更快

FastThreadLocal 在定位数据时直接用数组下标 index 取值,时间复杂度稳定在 O(1),不存在哈希冲突的问题。

每个 FastThreadLocal 实例在创建时,就通过 InternalThreadLocalMap.nextVariableIndex() 拿到一个全局唯一的自增 index,后续所有读写操作都基于这个 index 直接操作数组。

2.2 扩容更简单

ThreadLocal 扩容需要 rehash,FastThreadLocal 不需要。扩容时以当前 index 为基准,向上取整到 2 的幂次作为新容量,然后直接把原数组拷贝过去就行了。

2.3 内存安全性更高

FastThreadLocal 提供了 remove() 方法主动清除对象。更重要的是,在线程池场景下,Netty 封装了 FastThreadLocalRunnable,任务执行完之后会自动调用 FastThreadLocal.removeAll(),把 Set 集合里所有 FastThreadLocal 对象都清理掉,不用担心内存泄漏。

3. 底层结构:InternalThreadLocalMap

FastThreadLocal 的数据实际存在 InternalThreadLocalMap 里,它继承自 UnpaddedInternalThreadLocalMap

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
    private static final int DEFAULT_ARRAY_LIST_INITIAL_CAPACITY = 8;
    private static final int STRING_BUILDER_INITIAL_SIZE;
    private static final int STRING_BUILDER_MAX_SIZE;
    public static final Object UNSET = new Object();
    private BitSet cleanerFlags;

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

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

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

class UnpaddedInternalThreadLocalMap {
    static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();
    static final AtomicInteger nextIndex = new AtomicInteger();

几个关键点:

InternalThreadLocalMap示意

4. 和 JDK ThreadLocal 的对比

对比项 JDK ThreadLocal Netty FastThreadLocal
数据结构 哈希表 数组
查找复杂度 O(1) 理想,冲突时退化 稳定 O(1)
扩容方式 扩容 + rehash 扩容 + 数组拷贝
内存泄漏风险 有,需手动 remove 低,可自动清理
线程池支持 需手动处理 FastThreadLocalRunnable 自动清理