FastThreadLocal index 生成机制详解
问题背景
在 PooledByteBufAllocator 类的构造函数中:
threadCache = new PoolThreadLocalCache(useCacheForAllThreads);
这会调用 PoolThreadLocalCache 父类 FastThreadLocal 的构造函数:
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
那么这个 index 是怎么来的呢?
核心机制解析
1. nextIndex 静态变量
在 UnpaddedInternalThreadLocalMap 类中定义了一个静态的 AtomicInteger:
class UnpaddedInternalThreadLocalMap {
static final AtomicInteger nextIndex = new AtomicInteger();
// ...
}
关键点:
nextIndex是一个静态变量,在整个 JVM 进程中全局共享- 使用
AtomicInteger保证线程安全的自增操作 - 初始值为 0(AtomicInteger 默认值)
2. nextVariableIndex() 方法
在 InternalThreadLocalMap 类中实现:
public static int nextVariableIndex() {
int index = nextIndex.getAndIncrement(); // 原子性地获取当前值并自增
if (index < 0) {
nextIndex.decrementAndGet();
throw new IllegalStateException("too many thread-local indexed variables");
}
return index;
}
工作流程:
- 调用
getAndIncrement()原子性地获取当前值,并将nextIndex加 1 - 检查是否溢出(index < 0 表示超过 Integer.MAX_VALUE)
- 如果溢出,回退并抛出异常
- 返回获取到的 index 值
3. FastThreadLocal 构造函数
public class FastThreadLocal<V> {
// 第一个 FastThreadLocal 实例会占用 index = 0
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
private final int index; // 每个实例的唯一索引
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
}
完整的 index 分配流程
示例场景
假设我们创建多个 FastThreadLocal 实例:
// 第一次:FastThreadLocal 类加载时
// variablesToRemoveIndex = nextIndex.getAndIncrement() = 0
// nextIndex 变为 1
// 创建第一个 FastThreadLocal 实例
FastThreadLocal<String> ftl1 = new FastThreadLocal<>();
// ftl1.index = nextIndex.getAndIncrement() = 1
// nextIndex 变为 2
// 创建第二个 FastThreadLocal 实例
FastThreadLocal<Integer> ftl2 = new FastThreadLocal<>();
// ftl2.index = nextIndex.getAndIncrement() = 2
// nextIndex 变为 3
// 创建 PoolThreadLocalCache(继承自 FastThreadLocal)
PoolThreadLocalCache cache = new PoolThreadLocalCache(true);
// cache.index = nextIndex.getAndIncrement() = 3
// nextIndex 变为 4
index 分配表
| 实例 | index 值 | 说明 |
|---|---|---|
| FastThreadLocal.variablesToRemoveIndex | 0 | 类加载时分配,用于存储需要清理的 FastThreadLocal 列表 |
| 第一个 FastThreadLocal 实例 | 1 | 第一个用户创建的实例 |
| 第二个 FastThreadLocal 实例 | 2 | 第二个用户创建的实例 |
| PoolThreadLocalCache 实例 | 3 | PooledByteBufAllocator 中的 threadCache |
| … | … | 依次递增 |
index 的作用
1. 作为数组索引
每个线程都有一个 InternalThreadLocalMap,其中包含一个 Object[] 数组:
class UnpaddedInternalThreadLocalMap {
Object[] indexedVariables; // 存储线程本地变量的数组
}
index 就是这个数组的索引位置:
public Object indexedVariable(int index) {
Object[] lookup = indexedVariables;
return index < lookup.length ? lookup[index] : UNSET;
}
public boolean setIndexedVariable(int index, Object value) {
Object[] lookup = indexedVariables;
if (index < lookup.length) {
Object oldValue = lookup[index];
lookup[index] = value; // 使用 index 作为数组索引
return oldValue == UNSET;
} else {
expandIndexedVariableTableAndSet(index, value);
return true;
}
}
2. 快速访问
通过 index 直接访问数组元素,时间复杂度为 O(1),比 JDK 的 ThreadLocal 使用哈希表更快。
关键设计要点
1. 全局唯一性
nextIndex是静态变量,保证每个FastThreadLocal实例都有唯一的index- 即使在不同线程中创建,
index也是全局递增的
2. 线程安全
- 使用
AtomicInteger保证多线程环境下的安全自增 - 无需额外的同步机制
3. 空间换时间
- 每个线程维护一个数组,空间开销较大
- 但访问速度极快,适合高性能场景
4. 动态扩容
private void expandIndexedVariableTableAndSet(int index, Object value) {
Object[] oldArray = indexedVariables;
final int oldCapacity = oldArray.length;
int newCapacity = index;
// 使用位运算计算新容量(向上取到最近的 2 的幂次)
newCapacity |= newCapacity >>> 1;
newCapacity |= newCapacity >>> 2;
newCapacity |= newCapacity >>> 4;
newCapacity |= newCapacity >>> 8;
newCapacity |= newCapacity >>> 16;
newCapacity++;
Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
newArray[index] = value;
indexedVariables = newArray;
}
总结
FastThreadLocal 的 index 生成机制:
- 全局计数器:使用静态的
AtomicInteger nextIndex作为全局计数器 - 原子自增:每次创建
FastThreadLocal实例时,调用getAndIncrement()获取唯一索引 - 数组索引:
index用作每个线程的indexedVariables数组的索引位置 - 高性能访问:通过数组索引直接访问,避免哈希计算和冲突处理
445

被折叠的 条评论
为什么被折叠?



