ThreadLocalMap 是 ThreadLocal
的核心底层数据结构,负责在每个线程中存储与 ThreadLocal
实例绑定的数据。它的设计目标是高效管理线程隔离数据,同时尽量减少内存泄漏风险。以下是其核心实现细节。
数据结构与设计目标
核心结构
Entry 数组:
ThreadLocalMap
内部维护一个Entry[]
数组,每个Entry
表示一个键值对。static class Entry extends WeakReference<ThreadLocal<?>> { Object value; // 值(强引用) Entry(ThreadLocal<?> k, Object v) { super(k); // Key 是弱引用(继承自 WeakReference) value = v; } }
Key:
ThreadLocal
实例(弱引用,避免内存泄漏)。Value:线程绑定的数据(强引用,需手动或惰性清理)。
哈希算法:
通过ThreadLocal.threadLocalHashCode
计算索引位置(类似取模运算):int i = key.threadLocalHashCode & (table.length - 1); // table.length 是 2 的幂
设计目标
线程隔离:每个线程独立维护自己的
ThreadLocalMap
。内存高效:通过弱引用和惰性清理减少内存泄漏。
低冲突率:使用线性探测法(开放寻址)处理哈希冲突。
哈希冲突解决:开放寻址法(线性探测法)
与 HashMap
的链表法不同,ThreadLocalMap
使用 开放寻址法(线性探测法) 解决冲突:
插入流程:
计算初始索引
i = hash & (len-1)
。若
table[i]
已被占用(Key 不同),则向后遍历(i = nextIndex(i, len)
)直到找到空槽。
查找流程:
计算初始索引
i
,若table[i]
的 Key 不匹配,则向后遍历直到找到目标或空槽。
删除流程:
清理当前槽位,并触发探测式清理(
expungeStaleEntry
),避免后续查找因空槽中断。
示例:插入一个 Key 到冲突位置
// 假设 table[3] 已被占用,Key 不同
hash = key.threadLocalHashCode;
i = hash & (len-1); // 初始计算为 3
while (table[i] != null) {
i = nextIndex(i, len); // 线性探测,i=4,5,6...
}
table[i] = new Entry(key, value);
内存管理机制
惰性清理(Lazy Cleanup)
在以下操作中触发清理无效 Entry(Key 为 null
的 Entry):
getEntry(ThreadLocal<?> key)
:查找时发现 Key 已被回收,触发清理。set(ThreadLocal<?> key, Object value)
:插入时遇到无效 Entry,触发清理。remove(ThreadLocal<?> key)
:直接清理指定 Entry。
核心方法 expungeStaleEntry(int staleSlot)
:
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 1. 清理当前槽位
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 2. 向后探测清理连续段中的无效 Entry
Entry e;
int i;
for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) { // 该 Entry 本应位于其他位置(因冲突被挤到此处)
tab[i] = null; // 清空当前槽位
// 重新哈希到正确位置
while (tab[h] != null) h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
扩容机制
触发条件:
当插入新 Entry 后size >= threshold
(阈值 = 容量 * 2/3),且惰性清理后仍无法腾出空间。扩容流程:
容量翻倍(如 16 → 32),创建新数组。
遍历旧数组,重新哈希有效 Entry 到新数组。
更新阈值(新容量 * 2/3)。
与 HashMap 的对比
典型应用场景
线程隔离数据存储:
每个线程的数据库连接、事务上下文等。
性能优化:
避免线程间竞争(如 SimpleDateFormat 的线程安全封装)。
框架级使用:
Spring 的
RequestContextHolder
、TransactionSynchronizationManager
。
注意事项与最佳实践
避免内存泄漏:
使用完
ThreadLocal
后必须调用remove()
,尤其是线程池环境。避免使用静态
ThreadLocal
(静态变量强引用 Key,导致弱引用失效)。
减少哈希冲突:
控制每个线程的
ThreadLocal
实例数量。
谨慎扩容:
频繁扩容会影响性能,初始化时预估合理容量。
总结
ThreadLocalMap
是 ThreadLocal
实现线程隔离存储的核心,其通过 弱引用 Key、开放寻址法(线性探测法) 和 惰性清理机制,在保证高效访问的同时降低内存泄漏风险。开发者需理解其底层逻辑,正确使用 remove()
方法,才能在高并发场景下安全高效地管理线程局部变量。