一、核心属性
1. threadLocalHashCode
java
private final int threadLocalHashCode = nextHashCode();
- 每个 ThreadLocal 实例都有一个唯一的 threadLocalHashCode,它在对象创建时通过 nextHashCode() 生成。
- 这个哈希码用于在 ThreadLocalMap 中定位该 ThreadLocal 对应的值所在的桶索引。
2. nextHashCode 与 HASH_INCREMENT
java
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
- · nextHashCode 是一个 AtomicInteger 类型的静态变量,用于生成全局唯一的哈希码。
- · HASH_INCREMENT 是一个魔数(0x61c88647),它是斐波那契散列乘数,可以使生成的哈希码在 2 的幂次方数组上均匀分布,减少哈希冲突。
- · 每次创建新的 ThreadLocal 实例时,都会调用 nextHashCode() 将当前 nextHashCode 加上 HASH_INCREMENT,从而保证每个实例的哈希码唯一且分布均匀。
二、重要内部类:ThreadLocalMap
ThreadLocalMap 是 ThreadLocal 的静态内部类,它是真正存储线程局部变量的地方。每个线程(Thread 对象)都持有一个 ThreadLocalMap 类型的引用(threadLocals)。
1. Entry 节点
java
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
- · Entry 继承自 WeakReference<ThreadLocal<?>>,键(ThreadLocal 实例)被弱引用持有,值(value)是强引用。
- · 这样设计是为了避免内存泄漏:当 ThreadLocal 对象不再被外部强引用时,可以被 GC 回收,此时 Entry 的键变为 null,后续可以清除该 Entry。
- · Entry 数组是 ThreadLocalMap 存储数据的核心结构。
2. ThreadLocalMap 的核心属性
java
private Entry[] table; // 哈希表,长度必须是 2 的幂
private int size; // 实际存储的 Entry 数量
private int threshold; // 扩容阈值,默认是 table 长度的 2/3
- · table 是一个 Entry 数组,用于存储键值对。
- · size 记录当前 Entry 的数量。
- · threshold 决定何时扩容,当 size >= threshold 时,会进行 rehash 操作(扩容为原来两倍并重新散列)。
3. 哈希冲突解决
ThreadLocalMap 采用开放地址法(线性探测)解决哈希冲突,而不是 HashMap 的链地址法。这是因为 Entry 数量通常较小,线性探测在缓存局部性上更有优势。
三、核心方法
1. set(T value):设置当前线程的局部变量
java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
- · 首先获取当前线程 t。
- · 调用 getMap(t) 获取线程的 threadLocals 变量。
- · 如果 map 已存在,则调用 map.set(this, value) 将当前 ThreadLocal 作为键,value 作为值存入。
- · 如果 map 不存在,则调用 createMap(t, value) 创建新的 ThreadLocalMap 并设置到线程中。
ThreadLocalMap.set() 的核心逻辑
java
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 计算桶索引
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { // 找到相同 key,直接替换 value
e.value = value;
return;
}
if (k == null) { // 遇到过期 Entry(key 已被 GC),执行替换
replaceStaleEntry(key, value, i);
return;
}
}
// 找到空槽,创建新 Entry
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash(); // 扩容或重新散列
}
- · 通过 key.threadLocalHashCode & (len-1) 计算初始桶索引。
- · 线性探测查找相同 key 或可用的空槽。
- · 如果遇到 key 为 null 的过期 Entry,则调用 replaceStaleEntry 进行替换,同时会清理其他过期槽。
- · 插入新 Entry 后,如果未清理到过期槽且 size >= threshold,则触发 rehash()(先尝试清理过期槽,若仍超过阈值的 3/4 则扩容)。
2. get():获取当前线程的局部变量
java
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
- · 获取当前线程的 map。
- · 如果 map 存在,调用 map.getEntry(this) 查找对应 Entry。
- · 如果找到,返回 Entry.value。
- · 如果 map 不存在或未找到,则调用 setInitialValue() 设置初始值并返回。
ThreadLocalMap.getEntry() 的核心逻辑
java
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
- · 先直接定位到哈希索引位置,如果该位置的 Entry 键匹配,直接返回。
- · 如果不匹配(可能是哈希冲突或 Entry 过期),则调用 getEntryAfterMiss 继续线性探测查找。
3. remove():移除当前线程的局部变量
java
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
- · 调用 map.remove(this) 删除当前 ThreadLocal 对应的 Entry。
- · remove 方法会找到 Entry 并将其引用置空(e.clear()),同时清理过期槽。
四、与 Thread 类的关联
每个 Thread 对象内部持有两个 ThreadLocalMap 类型的字段:
java
ThreadLocal.ThreadLocalMap threadLocals = null; // 普通 ThreadLocal
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // 可继承的 ThreadLocal
- · threadLocals 用于存储当前线程的所有 ThreadLocal 变量。
- · inheritableThreadLocals 用于存储可继承的 ThreadLocal 变量,当创建子线程时,会从父线程复制该 Map。
这两个字段通过 ThreadLocal 的 getMap(Thread t) 和 createMap(Thread t, T firstValue) 方法进行访问和初始化。
五、内存泄漏问题
Entry 使用 WeakReference<ThreadLocal<?>> 作为键,意味着如果外部不再持有 ThreadLocal 对象的强引用,GC 回收 ThreadLocal 后,Entry 的键会变为 null。但 Entry.value 仍然是强引用,如果线程持续存活(如线程池中的线程),这些 value 就可能无法被回收,造成内存泄漏。