ThreadLocal 核心源码解析:属性、内部类与重要接口深度剖析

一、核心属性

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

ThreadLocalMapThreadLocal 的静态内部类,它是真正存储线程局部变量的地方。每个线程(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 就可能无法被回收,造成内存泄漏。

相关推荐
lUie INGA5 小时前
在2023idea中如何创建SpringBoot
java·spring boot·后端
geBR OTTE5 小时前
SpringBoot中整合ONLYOFFICE在线编辑
java·spring boot·后端
Porunarufu5 小时前
博客系统UI自动化测试报告
java
Aurorar0rua6 小时前
CS50 x 2024 Notes C - 05
java·c语言·数据结构
Cosmoshhhyyy7 小时前
《Effective Java》解读第49条:检查参数的有效性
java·开发语言
布谷歌7 小时前
常见的OOM错误 ( OutOfMemoryError全类型详解)
java·开发语言
eLIN TECE7 小时前
springboot和springframework版本依赖关系
java·spring boot·后端
老神在在0018 小时前
Spring Bean 的六种作用域详解
java·后端·spring
仙草不加料8 小时前
互联网大厂Java面试故事实录:三轮场景化技术提问与详细答案解析
java·spring boot·微服务·面试·aigc·电商·内容社区
程序员老邢8 小时前
【技术底稿 19】Redis7 集群密码配置 + 权限锁死 + 磁盘占满连锁故障真实排查全记录
java·服务器·经验分享·redis·程序人生·微服务