14.Netty的FastThreadLocal
总结
- FastThreadLocal 用数组下标直接定位,O(1) 查找,比 JDK ThreadLocal 的哈希表更快
- 扩容也更简单,不需要 rehash,直接按 2 的幂次扩容拷贝
- 内存安全性更高,配合 FastThreadLocalRunnable 可以在线程池场景下自动清理
- 底层依赖 InternalThreadLocalMap,每个 FastThreadLocal 实例在创建时就分配好了固定的数组下标
1. 为什么要有 FastThreadLocal?
JDK 的 ThreadLocal 用起来没什么大问题,但在 Netty 这种高并发、高频率创建线程局部变量的场景下,它的性能瓶颈就暴露出来了:
- 哈希冲突:ThreadLocal 底层是哈希表,数据多了容易冲突,用线性探测法解决冲突时要不停往下找,效率不稳定
- rehash 开销:扩容时要对所有 key 重新计算哈希,代价比较大
- 内存泄漏风险:ThreadLocal 的 key 是弱引用,value 是强引用,GC 后 key 变 null,value 却还在,只能等线程销毁才能释放
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();
几个关键点:
- 初始数组大小是 32,所有槽位用
UNSET对象填充,表示"未设置" nextIndex是全局自增的AtomicInteger,每个 FastThreadLocal 实例创建时各取一个,互不干扰- 如果当前线程是
FastThreadLocalThread,直接从线程对象上拿InternalThreadLocalMap;如果是普通线程,就退化成用slowThreadLocalMap(一个普通的 ThreadLocal)来存,性能会差一些

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