揭秘Netty高性能线程本地存储机制:FastThreadLocal

InternalThreadLocalMap

InternalThreadLocalMap 是 Netty 中一个非常核心的内部工具类,是一个最终类,用于存储 Netty 和所有 FastThreadLocal 的线程本地变量。

它为 Netty 自身的组件以及用户定义的 FastThreadLocal 变量提供了一个高性能的线程本地存储机制。它的设计目标是优化标准 java.lang.ThreadLocal 在高并发和大量线程本地变量场景下的性能。

核心设计思想

  1. 数组替代 ThreadLocalMap: JDK 的 ThreadLocal 内部使用一个 ThreadLocalMap (基于哈希表) 来存储每个线程的本地变量。当 ThreadLocal 实例很多时,哈希冲突和查找开销可能会增加。InternalThreadLocalMap 则主要使用一个对象数组Object[ ] (indexedVariables) 来存储这些变量。每个 FastThreadLocal 实例在创建时都会获得一个唯一的、全局递增的索引 (index),这个索引直接对应到 indexedVariables 数组的下标。这样,获取线程本地变量就变成了简单的数组访问操作,速度非常快。

  2. FastThreadLocalThread 优化: Netty 引入了 FastThreadLocalThread。如果当前线程是 FastThreadLocalThread 的实例,那么 InternalThreadLocalMap 实例会直接作为该线程的一个成员变量存在,访问速度更快,避免了 ThreadLocal.get() 的开销。

  3. 兼容普通线程: 对于非 FastThreadLocalThread 的普通线程,InternalThreadLocalMap 会退化为使用一个标准的 ThreadLocal (slowThreadLocalMap) 来存储其实例,保证了兼容性。

  4. 预定义和缓存常用对象InternalThreadLocalMap 内部还预定义了一些 Netty 核心组件会频繁使用的线程本地对象,例如 StringBuilderCharsetEncoder/CharsetDecoder 缓存、ArrayList 等。通过复用这些对象,可以减少对象创建和垃圾回收的开销。

静态成员

private static final ThreadLocal slowThreadLocalMap = new ThreadLocal<>();
private static final AtomicInteger nextIndex = new AtomicInteger();
public static final int VARIABLES_TO_REMOVE_INDEX = nextVariableIndex();
private static final int DEFAULT_ARRAY_LIST_INITIAL_CAPACITY = 8;
private static final int ARRAY_LIST_CAPACITY_EXPAND_THRESHOLD = 1 << 30;
private static final int ARRAY_LIST_CAPACITY_MAX_SIZE = Integer.MAX_VALUE - 8;
private static final int HANDLER_SHARABLE_CACHE_INITIAL_CAPACITY = 4;
private static final int INDEXED_VARIABLE_TABLE_INITIAL_SIZE = 32;
private static final int STRING_BUILDER_INITIAL_SIZE;
private static final int STRING_BUILDER_MAX_SIZE;
private static final InternalLogger logger;
public static final Object UNSET = new Object();
  • slowThreadLocalMap:普通线程使用的 ThreadLocal,用于存储 InternalThreadLocalMap 实例。
  • nextIndex:原子整数,用于生成下一个变量索引。
  • VARIABLES_TO_REMOVE_INDEX:需要移除的变量的索引。
  • 各种常量:定义了数组列表初始容量、扩展阈值、最大容量等配置。
  • STRING_BUILDER_INITIAL_SIZE 和 STRING_BUILDER_MAX_SIZEStringBuilder 的初始大小和最大大小,从系统属性获取。
  • logger:日志记录器。
  • UNSET:表示变量未设置的特殊对象。

实例成员

InternalThreadLocalMap.java

private Object[] indexedVariables;
private int futureListenerStackDepth;
private int localChannelReaderStackDepth;
private Map, Boolean> handlerSharableCache;
private Map, TypeParameterMatcher> typeParameterMatcherGetCache;
private Map, Map> typeParameterMatcherFindCache;
private StringBuilder stringBuilder;
private Map charsetEncoderCache;
private Map charsetDecoderCache;
private ArrayList arrayList;
private BitSet cleanerFlags;
 
  
  • indexedVariables:用于存储线程本地变量的数组。
  • futureListenerStackDepth 和 localChannelReaderStackDepth:分别表示未来监听器栈深度和本地通道读取器栈深度。
  • 各种缓存:用于存储处理器共享信息、类型参数匹配器等。
  • stringBuilder:线程本地的 StringBuilder
  • charsetEncoderCache 和 charsetDecoderCache:字符集编码和解码器缓存。
  • arrayList:线程本地的 ArrayList
  • cleanerFlags:用于标记清理标志的 BitSet

getIfSet() 和 get()

InternalThreadLocalMap.java

public static InternalThreadLocalMap getIfSet() {
    Thread thread = Thread.currentThread();
    if (thread instanceof FastThreadLocalThread) {
        return ((FastThreadLocalThread) thread).threadLocalMap();
    }
    return slowThreadLocalMap.get();
}

public static InternalThreadLocalMap get() {
    Thread thread = Thread.currentThread();
    if (thread instanceof FastThreadLocalThread) {
        return fastGet((FastThreadLocalThread) thread);
    } else {
        return slowGet();
    }
}
  • getIfSet():获取当前线程的 InternalThreadLocalMap 实例,如果不存在则返回 null
  • get():获取当前线程的 InternalThreadLocalMap 实例,如果不存在则创建一个新的实例。

fastGet() 和 slowGet()

InternalThreadLocalMap.java

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

private static InternalThreadLocalMap slowGet() {
    InternalThreadLocalMap ret = slowThreadLocalMap.get();
    if (ret == null) {
        ret = new InternalThreadLocalMap();
        slowThreadLocalMap.set(ret);
    }
    return ret;
}
  • fastGet():为 FastThreadLocalThread 快速获取或创建 InternalThreadLocalMap 实例。
  • slowGet():为普通线程获取或创建 InternalThreadLocalMap 实例。

setIndexedVariable() 和 getAndSetIndexedVariable()

InternalThreadLocalMap.java

public boolean setIndexedVariable(int index, Object value) {
    return getAndSetIndexedVariable(index, value) == UNSET;
}

public Object getAndSetIndexedVariable(int index, Object value) {
    Object[] lookup = indexedVariables;
    if (index < lookup.length) {
        Object oldValue = lookup[index];
        lookup[index] = value;
        return oldValue;
    }
    expandIndexedVariableTableAndSet(index, value);
    return UNSET;
}
  • setIndexedVariable():设置指定索引的线程本地变量,如果是新变量则返回 true
  • getAndSetIndexedVariable():设置指定索引的线程本地变量,并返回旧值。如果是新变量则返回 UNSET

expandIndexedVariableTableAndSet()

InternalThreadLocalMap.java

private void expandIndexedVariableTableAndSet(int index, Object value) {
    Object[] oldArray = indexedVariables;
    final int oldCapacity = oldArray.length;
    int newCapacity;
    if (index < ARRAY_LIST_CAPACITY_EXPAND_THRESHOLD) {
        newCapacity = index;
        newCapacity |= newCapacity >>>  1;
        newCapacity |= newCapacity >>>  2;
        newCapacity |= newCapacity >>>  4;
        newCapacity |= newCapacity >>>  8;
        newCapacity |= newCapacity >>> 16;
        newCapacity ++;
    } else {
        newCapacity = ARRAY_LIST_CAPACITY_MAX_SIZE;
    }

    Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
    Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
    newArray[index] = value;
    indexedVariables = newArray;
}

该方法用于扩展 indexedVariables 数组的容量,并设置指定索引的变量值。

关键实现细节

  1. UNSET 对象: 这是一个特殊的静态对象,用于表示 indexedVariables 数组中某个索引位置尚未被设置,或者已被移除。这比使用 null 更好,因为 null 本身可能是一个合法的线程本地变量值。

  2. nextVariableIndex() 和 index: 每个 FastThreadLocal 实例在构造时都会调用 InternalThreadLocalMap.nextVariableIndex() 来获取一个全局唯一的整数索引。这个索引直接用于在 InternalThreadLocalMap 的 indexedVariables 数组中定位该 FastThreadLocal 变量的值。

  3. 数组扩容 (expandIndexedVariableTableAndSet): 当一个 FastThreadLocal 的索引超出了当前 indexedVariables 数组的长度时,会触发数组扩容。扩容策略是找到一个大于等于当前索引的最小的2的幂次方作为新容量(有一个上限 ARRAY_LIST_CAPACITY_MAX_SIZE)。新数组会复制旧数组的内容,并将新增的部分用 UNSET 对象填充。

  4. VARIABLES_TO_REMOVE_INDEX: 这是一个特殊的索引,其对应的值是一个 Set>。这个集合用于存储那些需要被清理的 FastThreadLocal 实例。当调用 FastThreadLocal.removeAll() 或 FastThreadLocalThread 结束时,会遍历这个集合并移除相应的线程本地变量,防止内存泄漏。

  5. 预定义对象的复用:

    • stringBuilder(): 提供一个可复用的 StringBuilder。每次获取时,如果已存在,会清空其内容并检查容量是否过大。
    • charsetEncoderCache() / charsetDecoderCache(): 提供 CharsetEncoder 和 CharsetDecoder 的缓存,使用 IdentityHashMap 是因为 Charset 对象通常是单例的,可以安全地用作键。
    • arrayList(): 提供一个可复用的 ArrayList
    • handlerSharableCache(): 使用 WeakHashMap 缓存 ChannelHandler 是否标记为 @SharableWeakHashMap 的键是弱引用,当 Class 对象不再被其他地方强引用时,可以被垃圾回收,从而避免内存泄漏。

继承 UnpaddedInternalThreadLocalMap:

UnpaddedInternalThreadLocalMap.java

// ... existing code ...
/**
 * @deprecated This class will be removed in the future.
 */
class UnpaddedInternalThreadLocalMap {
    // We cannot remove this in 4.1 because it could break compatibility.
}

这个父类目前是空的,并且被标记为 @deprecated。它的存在主要是为了向后兼容。在早期版本中,InternalThreadLocalMap 可能包含一些用于避免伪共享 (false sharing) 的填充字段 (padding fields)。这些字段后来被移到了 UnpaddedInternalThreadLocalMap 中,然后 InternalThreadLocalMap 再继承它。

现在这些填充字段 (rp1 到 rp8) 也被标记为 @deprecated,并直接放在 InternalThreadLocalMap 中,说明 Netty 团队可能认为现代 JVM 和硬件对伪共享的处理已经足够好,或者有其他更有效的避免方式。

总结

InternalThreadLocalMap 通过使用基于索引的数组存储、针对 FastThreadLocalThread 的优化以及预定义对象的复用,为 Netty 提供了一个比标准 ThreadLocal 更高效的线程本地存储方案。它的设计精巧,充分考虑了性能、内存使用和兼容性。理解其内部机制对于深入理解 Netty 的高性能特性非常有帮助。

FastThreadLocal

FastThreadLocal 是 Netty 中对 Java 标准 ThreadLocal 的一种特殊优化实现。当在 FastThreadLocalThread 类型的线程中使用时,它能提供比标准 ThreadLocal 更高的访问性能。

下面是对其类结构和核心源码实现的分析:

类结构与核心目的

  • 目的:提供高性能的线程局部变量访问。
  • 泛型参数 :表示线程局部变量的类型。
  • 核心思想:与标准 ThreadLocal 使用哈希表定位变量不同,FastThreadLocal 为每个实例分配一个唯一的整数索引 (index)。在 FastThreadLocalThread 中,这些变量存储在一个数组 (InternalThreadLocalMap.indexedVariables) 中,通过索引直接访问,避免了哈希计算和潜在的哈希冲突,从而提高性能。
  • 降级兼容:如果在非 FastThreadLocalThread 线程中使用,FastThreadLocal 会回退到类似标准 ThreadLocal 的机制(通过 InternalThreadLocalMap 的慢速路径)。

核心成员变量

  • private final int index;
    • 这是 FastThreadLocal 最关键的实例变量。
    • 在 FastThreadLocal 对象构造时,通过 InternalThreadLocalMap.nextVariableIndex() 分配一个全局唯一的、自增的整数。
    • 此 index 直接用作 InternalThreadLocalMap 内部数组的下标,用于存取该线程的此 FastThreadLocal 变量的值。

构造函数

  • public FastThreadLocal()
    • 主要作用是初始化 index 字段:index = InternalThreadLocalMap.nextVariableIndex();

public final V get()

  • 获取当前线程中此 FastThreadLocal 变量的值。
  • 步骤:
    1. InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();: 获取当前线程关联的 InternalThreadLocalMap。如果是 FastThreadLocalThread,直接返回其成员;否则,通过一个静态的 ThreadLocal 获取。
    2. Object v = threadLocalMap.indexedVariable(index);: 使用 index 从 InternalThreadLocalMap 的内部数组中获取值。
    3. 如果 v != InternalThreadLocalMap.UNSET ( UNSET 是一个特殊静态对象,表示未设置或已移除),则直接转换并返回 v
    4. 如果 v == UNSET,则调用 initialize(threadLocalMap) 进行初始化。

private V initialize(InternalThreadLocalMap threadLocalMap)

  • 当 get() 发现变量未初始化时调用。
  • 步骤:
    1. v = initialValue();: 调用用户可覆盖的 protected V initialValue() throws Exception 方法获取初始值。默认返回 null
    2. 检查 v 不能是 InternalThreadLocalMap.UNSET
    3. threadLocalMap.setIndexedVariable(index, v);: 将初始值存入 InternalThreadLocalMap 的 index 位置。
    4. addToVariablesToRemove(threadLocalMap, this);: 将当前 FastThreadLocal 实例添加到一个集合中,该集合由 InternalThreadLocalMap 维护,用于 removeAll() 操作。
    5. 返回初始值 v

protected V initialValue() throws Exception

  • 返回此线程局部变量的初始值。子类可以覆盖此方法以提供非 null 的初始值。默认返回 null

set

public final void set(V value)

  • 设置当前线程中此 FastThreadLocal 变量的值。
  • 实际调用 public V getAndSet(V value)

public V getAndSet(V value)

  • 设置新值并返回旧值。
  • 步骤:
    1. 如果 value == InternalThreadLocalMap.UNSET,则视为移除操作,调用 removeAndGet(InternalThreadLocalMap.getIfSet())
    2. 否则,获取 InternalThreadLocalMap,然后调用 setKnownNotUnset(threadLocalMap, value)

private V setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value):

  1. V old = (V) threadLocalMap.getAndSetIndexedVariable(index, value);: 在 InternalThreadLocalMap 中设置新值并获取旧值。
  2. 如果 old == UNSET (表示之前未设置过),则调用 addToVariablesToRemove(threadLocalMap, this); 并返回 null (因为逻辑上的旧值是“未设置”)。
  3. 否则返回 old

public final void remove()

  • 移除当前线程中此 FastThreadLocal 变量。
  • 实际调用 remove(InternalThreadLocalMap.getIfSet())

private V removeAndGet(InternalThreadLocalMap threadLocalMap):

  1. 如果 threadLocalMap 为 null,返回 null
  2. Object v = threadLocalMap.removeIndexedVariable(index);: 从 InternalThreadLocalMap 中移除 index 位置的变量,并将其标记为 UNSET
  3. 如果 v != UNSET (表示确实移除了一个有效值):
    • removeFromVariablesToRemove(threadLocalMap, this);: 从待移除集合中移除当前实例。
    • onRemoval((V) v);: 调用用户可覆盖的 protected void onRemoval(V value) throws Exception 方法,用于资源清理。
    • 返回被移除的旧值 v
  4. 如果 v == UNSET,返回 null

    protected void onRemoval(V value) throws Exception

    • 当变量通过 remove() 方法被移除时调用。子类可以覆盖此方法以执行必要的清理操作。默认无操作。

    核心静态方法

    1. public static void removeAll()

      • 移除当前线程绑定的所有 FastThreadLocal 变量。
      • 获取当前线程的 InternalThreadLocalMap
      • 从 map 中获取之前通过 addToVariablesToRemove 注册的 FastThreadLocal 实例集合 (存储在 VARIABLES_TO_REMOVE_INDEX 特殊索引处)。
      • 遍历这个集合,对每个 FastThreadLocal 实例调用其 remove(threadLocalMap) 方法。
      • 最后,调用 InternalThreadLocalMap.remove() 来清理 InternalThreadLocalMap 本身(如果是慢速路径,会移除 ThreadLocal 中的 InternalThreadLocalMap)。
    2. public static int size()

      • 返回当前线程绑定的 FastThreadLocal 变量的数量。通过 InternalThreadLocalMap.getIfSet().size() 实现。
    3. public static void destroy()

      • 销毁用于非 FastThreadLocalThread 的 InternalThreadLocalMap 存储机制(即清除静态 ThreadLocal)。主要用于容器环境下防止内存泄漏。
    4. private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal variable)

      • 将一个 FastThreadLocal 实例添加到一个由 InternalThreadLocalMap 管理的 Set 中。这个 Set 存储在 threadLocalMap 的 VARIABLES_TO_REMOVE_INDEX 位置。此 Set 用于 removeAll() 操作。
    5. private static void removeFromVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal variable)

      • 从上述 Set 中移除一个 FastThreadLocal 实例。

    与 InternalThreadLocalMap 和 FastThreadLocalThread 的关系

    • InternalThreadLocalMap: 是实际存储线程局部变量的容器。它内部有一个 Object[] indexedVariables 数组,FastThreadLocal 的 index 就是这个数组的下标。
    • FastThreadLocalThread: 这种类型的线程直接拥有一个 InternalThreadLocalMap 实例作为其成员变量。当 FastThreadLocal 在这种线程上操作时,可以直接访问这个 InternalThreadLocalMap,并通过索引快速读写,这是“快速路径”。
    • 普通 Thread: 对于非 FastThreadLocalThreadInternalThreadLocalMap 实例通过一个静态的 ThreadLocal (名为 slowThreadLocalMap 在 InternalThreadLocalMap 类中) 来存储和获取,这是“慢速路径”,但功能上等价。

    总结

    FastThreadLocal 通过为每个实例分配一个唯一的数组索引,并结合 FastThreadLocalThread 直接持有 InternalThreadLocalMap 的设计,实现了对线程局部变量的 O(1) 访问。这在频繁访问线程局部变量的场景下(如Netty的事件循环中)能带来显著的性能提升。同时,它也提供了与标准 ThreadLocal 类似的回调方法 (initialValueonRemoval) 和管理方法 (removeAlldestroy)。

    FastThreadLocalThread

    我们来深入分析 FastThreadLocalThread 类的结构和实现源码。这个类是 Netty 高性能线程本地存储 (FastThreadLocal) 的核心组件之一。

    FastThreadLocalThread.java

    public class FastThreadLocalThread extends Thread {
        // 日志记录器
        private static final InternalLogger logger = ...;
        
        // 用于存储被当作 FastThreadLocalThread 处理的线程 ID 数组
        private static final AtomicReference fallbackThreads = new AtomicReference<>(null);
        
        // 标记是否会在 run() 完成后清理 FastThreadLocal
        private final boolean cleanupFastThreadLocals;
        
        // 存储线程本地变量的核心数据结构
        private InternalThreadLocalMap threadLocalMap;
        
        // 多个构造函数...
        
        // 核心方法...
    }
    

    关键成员分析

    1. threadLocalMap (核心成员)

      • 类型: InternalThreadLocalMap
      • 作用: 存储该线程的所有 FastThreadLocal 变量
      • 访问方式: 通过 threadLocalMap() 和 setThreadLocalMap() 方法
      • 线程安全: 这两个方法会检查当前线程是否是自身,如果不是会记录警告日志
    2. cleanupFastThreadLocals

      • 类型: boolean
      • 作用: 标记是否在 run() 方法完成后自动清理 FastThreadLocal 变量
      • 初始化: 在构造函数中根据是否包装了 Runnable 决定
    3. fallbackThreads

      • 类型: AtomicReference
      • 作用: 存储那些不能继承 FastThreadLocalThread 但希望获得类似性能优化的线程 ID (如虚拟线程)

    构造函数

    java

    // 基本构造函数
    public FastThreadLocalThread() {
        cleanupFastThreadLocals = false; // 不自动清理
    }
    
    // 带 Runnable 的构造函数
    public FastThreadLocalThread(Runnable target) {
        super(FastThreadLocalRunnable.wrap(target)); // 包装 Runnable
        cleanupFastThreadLocals = true; // 会自动清理
    }
    
    // 其他构造函数类似...
    

    关键点:

    • 所有接受 Runnable 参数的构造函数都会用 FastThreadLocalRunnable.wrap() 进行包装
    • 包装后的 Runnable 会在执行完成后自动清理 FastThreadLocal 变量

    threadLocalMap() 和 setThreadLocalMap()

    java

    public final InternalThreadLocalMap threadLocalMap() {
        // 安全检查:只有当前线程才能访问自己的 threadLocalMap
        if (this != Thread.currentThread() && logger.isWarnEnabled()) {
            logger.warn(new RuntimeException("It's not thread-safe..."));
        }
        return threadLocalMap;
    }
    
    public final void setThreadLocalMap(InternalThreadLocalMap threadLocalMap) {
        // 同样的安全检查
        if (this != Thread.currentThread() && logger.isWarnEnabled()) {
            logger.warn(new RuntimeException("It's not thread-safe..."));
        }
        this.threadLocalMap = threadLocalMap;
    }
    

    清理相关方法

    java

    // 判断当前线程是否会清理 FastThreadLocal
    public static boolean currentThreadWillCleanupFastThreadLocals() {
        Thread currentThread = currentThread();
        if (currentThread instanceof FastThreadLocalThread) {
            return ((FastThreadLocalThread) currentThread).willCleanupFastThreadLocals();
        }
        return isFastThreadLocalVirtualThread(); // 检查是否是虚拟线程
    }
    
    // 判断线程是否支持 FastThreadLocal
    public static boolean currentThreadHasFastThreadLocal() {
        return currentThread() instanceof FastThreadLocalThread || 
               isFastThreadLocalVirtualThread();
    }
    
        private static boolean isFastThreadLocalVirtualThread() {
            long[] arr = fallbackThreads.get();
            if (arr == null) {
                return false;
            }
            return Arrays.binarySearch(arr, Thread.currentThread().getId()) >= 0;
        }
    

    runWithFastThreadLocal() (重要!)

    java

    public static void runWithFastThreadLocal(Runnable runnable) {
        Thread current = currentThread();
        if (current instanceof FastThreadLocalThread) {
            throw new IllegalStateException("Caller is a real FastThreadLocalThread");
        }
        
        // 1. 将当前线程ID添加到 fallbackThreads
        long id = current.getId();
        fallbackThreads.updateAndGet(arr -> {
            if (arr == null) return new long[]{id};
            
            // 检查是否已存在 (避免重入)
            int index = Arrays.binarySearch(arr, id);
            if (index >= 0) throw new IllegalStateException("Reentrant call to run()");
            
            // 插入到正确位置 (保持数组有序)
            index = ~index;
            long[] next = new long[arr.length + 1];
            System.arraycopy(arr, 0, next, 0, index);
            next[index] = id;
            System.arraycopy(arr, index, next, index + 1, arr.length - index);
            return next;
        });
        
        try {
            runnable.run();
        } finally {
            // 2. 从 fallbackThreads 中移除当前线程ID
            fallbackThreads.getAndUpdate(arr -> {
                if (arr == null || (arr.length == 1 && arr[0] == id)) {
                    return null;
                }
                int index = Arrays.binarySearch(arr, id);
                if (index < 0) return arr;
                
                long[] next = new long[arr.length - 1];
                System.arraycopy(arr, 0, next, 0, index);
                System.arraycopy(arr, index + 1, next, index, arr.length - index - 1);
                return next;
            });
            
            // 3. 清理所有 FastThreadLocal 变量
            FastThreadLocal.removeAll();
        }
    }
    

    这个方法允许普通线程(包括虚拟线程)临时获得 FastThreadLocal 支持,执行完成后会自动清理。

    permitBlockingCalls()

    java

    public boolean permitBlockingCalls() {
        return false; // 默认禁止阻塞调用
    }
    

    这是一个钩子方法,子类可以覆盖。Netty 的事件循环线程会利用这个方法检查是否允许阻塞调用。

    设计要点

    1. 性能优化:

      • 直接继承 Thread 并添加 InternalThreadLocalMap 字段,避免了 ThreadLocal 的哈希表查找
      • 使用数组索引直接访问变量 (通过 InternalThreadLocalMap)
    2. 资源管理:

      • 通过 FastThreadLocalRunnable 包装确保资源清理
      • 提供了 runWithFastThreadLocal() 支持非 FastThreadLocalThread 线程
    3. 线程安全:

      • 严格限制只有当前线程能访问自己的 threadLocalMap
      • fallbackThreads 使用 AtomicReference 保证线程安全
    4. 扩展性:

      • permitBlockingCalls() 方法允许子类自定义行为
      • 支持虚拟线程等新特性

    与相关类的协作

    1. FastThreadLocal:

      • 通过 InternalThreadLocalMap 存储变量
      • 依赖 FastThreadLocalThread 提供快速访问路径
    2. InternalThreadLocalMap:

      • 作为 FastThreadLocalThread 的成员存储所有线程本地变量
      • 使用数组索引而非哈希表提高访问速度
    3. FastThreadLocalRunnable:

      • 包装用户 Runnable 确保 FastThreadLocal 清理

      java

      final class FastThreadLocalRunnable implements Runnable {
          private final Runnable runnable;
          
          public void run() {
              try {
                  runnable.run();
              } finally {
                  FastThreadLocal.removeAll(); // 确保清理
              }
          }
      
      static Runnable wrap(Runnable runnable) {
          return runnable instanceof FastThreadLocalRunnable ? runnable 
                 : new FastThreadLocalRunnable(runnable); // 已包装则直接返回
      }
      }
      

    这个类的设计充分体现了 Netty 对性能的极致追求,同时保持了良好的资源管理和线程安全性。

    为什么Recycler  使用 FastThreadLocal

    在 Netty 的 Recycler 中使用 FastThreadLocal 而不是普通 ThreadLocal 主要基于以下几个关键设计考量:


    1. 性能优化

    FastThreadLocal 相比标准 ThreadLocal 有以下性能优势:

    • 数组索引访问FastThreadLocal 使用固定索引直接访问数组元素,而 ThreadLocal 需要哈希计算和可能的哈希冲突处理
    • 避免哈希表开销:标准 ThreadLocal 使用 ThreadLocalMap(基于哈希表),而 FastThreadLocal 直接使用 InternalThreadLocalMap 的数组结构

    在 Recycler 高频对象回收场景下,这种优化能显著减少线程本地存储的访问开销。


    2. 内存效率

    • 减少对象创建FastThreadLocal 的数组存储方式比 ThreadLocalMap 的 Entry 对象更节省内存
    • 预分配空间InternalThreadLocalMap 会预分配固定大小的数组,避免动态扩容带来的性能波动

    对于需要维护大量回收对象的 Recycler 来说,内存效率至关重要。


    3. 与 Netty 线程模型的深度集成

    • FastThreadLocalThread 优化:当线程是 FastThreadLocalThread 类型时,InternalThreadLocalMap 会直接存储在线程对象中,完全避免了 ThreadLocal.get() 的查找开销
    • 虚拟线程支持:通过 fallbackThreads 机制,Recycler 也能在虚拟线程中保持较好的性能

    Netty 的默认线程工厂 (DefaultThreadFactory) 创建的线程都是 FastThreadLocalThread,这使得 Recycler 能获得最佳性能。


    4. 资源清理的确定性

    FastThreadLocal 提供了更可靠的资源清理机制:

    java

    private final FastThreadLocal> threadLocal = new FastThreadLocal>() {
        @Override
        protected void onRemoval(LocalPool value) throws Exception {
            super.onRemoval(value);
            // 确保线程终止时清理所有资源
            value.pooledHandles = null;
            value.owner = null;
            handles.clear();
        }
    };
    

    相比 ThreadLocalFastThreadLocal 能更可靠地触发清理回调(通过 FastThreadLocalRunnable 包装)。


    5. 与 Recycler 设计的一致性

    Recycler 的核心设计理念是避免任何形式的同步开销,这与 FastThreadLocal 的设计目标完全吻合:

    • 对象获取 (get()) 和回收 (recycle()) 都依赖线程本地操作
    • LocalPool 的批量处理机制 (chunkSize) 需要高效访问线程本地队列

    为什么不直接用 ThreadLocal

    1. 哈希计算开销:每次 get() 都需要计算哈希码,在对象池高频访问场景下影响显著
    2. 内存占用高ThreadLocalMap.Entry 是对象节点,比数组存储占用更多内存
    3. 清理不可靠:标准 ThreadLocal 依赖 Thread.exit() 清理资源,而 Netty 需要更确定的清理时机
    4. 无法利用 FastThreadLocalThread 优化:会失去直接访问线程字段的性能优势

    性能对比场景

    在 Recycler.get() 方法中:

    public final T get() {
        if (maxCapacityPerThread == 0 || 
            (PlatformDependent.isVirtualThread(Thread.currentThread()) &&
             !FastThreadLocalThread.currentThreadHasFastThreadLocal())) {
            return newObject((Handle) NOOP_HANDLE); // 回退路径
        }
        LocalPool localPool = threadLocal.get(); // 快速路径
        // ...
    }
    

    当线程是 FastThreadLocalThread 时,threadLocal.get() 实际上是:

    1. 直接从线程对象获取 InternalThreadLocalMap
    2. 通过预计算的 index 直接数组访问

    而标准 ThreadLocal 需要:

    1. 获取当前线程的 ThreadLocalMap
    2. 计算哈希槽
    3. 处理可能的哈希冲突

    总结

    Recycler 使用 FastThreadLocal 的核心原因是其与 Netty 高性能架构的深度契合:

    1. 为高频访问的线程本地操作提供 O(1) 的稳定访问性能
    2. 减少内存占用,这对维护大量回收对象的对象池至关重要
    3. 与 Netty 的线程模型和资源管理机制无缝集成
    4. 在保持 API 简洁的同时提供确定性的资源清理

    这种设计使得 Netty 的对象回收机制能在高并发场景下保持极高的性能,同时避免传统 ThreadLocal 的内存泄漏风险。

    你可能感兴趣的:(Netty,数据结构,并发/JUC,java,算法,数据结构,nio,开发语言,设计模式)