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 就可能无法被回收,造成内存泄漏。

相关推荐
JamesYoung79712 小时前
第七部分 — 存储 数据建模与迁移提示
java·开发语言·数据结构
大志学java2 小时前
idea中切换分支后,项目目录不显示的问题
java·elasticsearch·intellij-idea
程序员敲代码吗2 小时前
进程与线程:操作系统中的核心组件
java·开发语言
Java面试题总结2 小时前
java面试题及答案(基础题122道)
java·开发语言·jvm·spring·spring cloud·golang·java-ee
jacsonchen2 小时前
【MySQL】环境变量配置
java
番茄去哪了2 小时前
黑马点评实战篇千字总结
java·分布式·面向对象编程
pupudawang2 小时前
Java进阶——IO 流
java·开发语言·python
逆境不可逃2 小时前
【从零入门23种设计模式19】行为型之观察者模式
java·开发语言·算法·观察者模式·leetcode·设计模式·动态规划
一只鹿鹿鹿2 小时前
研发中心数据安全管理规定(文件)
java·运维·开发语言·数据库·后端