Skip to content

ThreadLocal 实现原理

API 用起来简单,内部结构却不简单。

理解 ThreadLocal 的实现原理,有助于你在出问题时快速定位,也能在阅读 Spring、MyBatis 等框架源码时不至于两眼一抹黑。

核心组件关系

┌──────────────────────────────────────────────────────────┐
│                      ThreadLocal 实现结构                 │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  ThreadLocal<T>                                         │
│  ┌────────────────────────────────────────────────┐      │
│  │  public T get()                                 │      │
│  │  public void set(T value)                        │      │
│  │  public void remove()                           │      │
│  │  protected T initialValue()                    │      │
│  └────────────────────────────────────────────────┘      │
│                           │                              │
│                           ▼                              │
│  ┌────────────────────────────────────────────────┐      │
│  │  ThreadLocal.ThreadLocalMap(内部类)           │      │
│  │  ┌──────────────────────────────────────────┐ │      │
│  │  │  Entry[] table                           │ │      │
│  │  │  ┌─────────┬─────────┬─────────┐        │ │      │
│  │  │  │ Entry   │ Entry   │ Entry   │ ...    │ │      │
│  │  │  │ key*    │ key*    │ key*    │        │ │      │
│  │  │  │ value   │ value   │ value   │        │ │      │
│  │  │  └─────────┴─────────┴─────────┘        │ │      │
│  │  └──────────────────────────────────────────┘ │      │
│  └────────────────────────────────────────────────┘      │
│                           │                              │
│                           ▼                              │
│  Thread                                                         │
│  ┌────────────────────────────────────────────────┐      │
│  │  ThreadLocal.ThreadLocalMap threadLocals       │      │
│  └────────────────────────────────────────────────┘      │
│                                                          │
│  存储在 Thread 对象内部,ThreadLocal 只是访问入口          │
│                                                          │
└──────────────────────────────────────────────────────────┘

三个关键点:

  1. ThreadLocal 是访问入口,本身不存储数据
  2. 数据存储在 Thread.threadLocals 中(ThreadLocalMap 类型)
  3. 每个 Thread 都有自己的 ThreadLocalMap,天然隔离

ThreadLocalMap 结构

java
// ThreadLocalMap 是 ThreadLocal 的静态内部类
static class ThreadLocalMap {

    // Entry 继承 WeakReference,key 为 ThreadLocal
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);   // 弱引用
            value = v;  // 强引用
        }
    }

    private Entry[] table;   // Entry 数组,核心存储结构
    private int size = 0;    // 实际元素数量
    private int threshold;   // 扩容阈值(默认 length * 2/3)
}

get() 源码解析

java
public T get() {
    // 1. 获取当前线程
    Thread t = Thread.currentThread();

    // 2. 获取当前线程的 ThreadLocalMap
    ThreadLocalMap map = getMap(t);  // 等价于 t.threadLocals

    // 3. map 存在,查找 Entry
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T) e.value;
            return result;
        }
    }

    // 4. map 不存在 或 找不到 Entry → 设置初始值
    return setInitialValue();
}

private T setInitialValue() {
    // 调用 initialValue() 获取初始值
    T value = initialValue();

    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);

    if (map != null) {
        map.set(this, value);  // 存入 map
    } else {
        createMap(t, value);    // 首次访问时创建 map
    }

    return value;
}

流程图:

get()

  ├─► Thread.currentThread()

  ├─► getMap(t) → t.threadLocals

  ├─► map.getEntry(this) 查找
  │    │
  │    ├─► 找到 → 返回 value
  │    └─► 找不到 → setInitialValue()

  └─► setInitialValue()
       ├─► 调用 initialValue()
       ├─► 创建 ThreadLocalMap(如需要)
       └─► 存入初始值

set() 源码解析

java
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);

    if (map != null) {
        // map 已存在,直接存入
        map.set(this, value);
    } else {
        // 首次 set,创建 map
        createMap(t, value);
    }
}

// createMap 等价于:
// t.threadLocals = new ThreadLocalMap(this, firstValue);

流程图:

set(value)

  ├─► Thread.currentThread()

  ├─► getMap(t)

  ├─► map != null → map.set(this, value)
  │    │
  │    └─► 线性探测法处理哈希冲突

  └─► map == null → createMap(t, value)

       └─► t.threadLocals = new ThreadLocalMap(this, value)

remove() 源码解析

java
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        m.remove(this);  // 删除当前 ThreadLocal 对应的 Entry
    }
}

// ThreadLocalMap.remove(key) 内部:
// 1. 通过哈希找到位置
// 2. 调用 e.clear() 清除弱引用(key 置 null)
// 3. 调用 expungeStaleEntry() 清理 value 并重新哈希

expungeStaleEntry() 的工作:

expungeStaleEntry(staleSlot)

  ├─► table[staleSlot].value = null   ← 释放 value
  ├─► table[staleSlot] = null         ← 释放 Entry
  ├─► 遍历后续 Entry,重新哈希(填补空位)
  └─► size--

哈希算法:黄金分割哈希

ThreadLocal 使用了一个精心设计的哈希算法,避免哈希冲突:

java
// ThreadLocal 的哈希码生成
private final int threadLocalHashCode = nextHashCode();

// 每次创建新的 ThreadLocal 都递增
private static AtomicInteger nextHashCode =
    new AtomicInteger();

// 固定增量:0x61c88647(黄金分割数)
private static final int HASH_INCREMENT = 0x61c88647;

为什么选 0x61c88647?因为它的二进制是 32 位的黄金分割比例,用它递增生成的哈希码在数组中分布均匀,减少冲突。

tl1 → hash: 0x61c88647
tl2 → hash: 0xc3910c8e
tl3 → hash: 0x12595931
...

哈希冲突解决:线性探测

ThreadLocalMap 使用开放地址法(线性探测)解决哈希冲突:

假设 table length = 8,插入三个 ThreadLocal:
index = hash & (len - 1)

tl1: index = 0 → table[0]
tl2: index = 3 → table[3]
tl3: index = 0(冲突)→ table[1](线性探测到第一个空位)

查找时也是线性探测:

getEntry(key)

  ├─► 计算 index = key.threadLocalHashCode & (len-1)

  ├─► table[i] == key → 找到

  ├─► table[i] == null → 不存在

  └─► 冲突 → i++ 继续探测

扩容机制

当 table 中的 Entry 数量超过阈值的 2/3 时,触发 rehash 扩容:

java
private void rehash() {
    // 先全量清理 stale Entry
    expungeStaleEntries();

    // 如果清理后 size >= threshold - threshold/4,扩容
    if (size >= threshold - threshold / 4)
        resize();
}

private void resize() {
    // 容量翻倍
    Entry[] newTable = new Entry[oldCapacity * 2];

    // 重新哈希所有 Entry
    for (Entry e : oldTable) {
        if (e != null) {
            // ...
            newTable[newIndex] = e;
        }
    }
}

内存泄漏防护机制回顾

ThreadLocalMap 的防护设计:

机制说明
Entry 用弱引用存 keyThreadLocal 置 null 后,key 可被 GC
expungeStaleEntry()清理 key==null 的 Entry,释放 value
触发时机get() / set() / remove() 时延迟清理
不等于安全仍需手动 remove()

一句话总结原理

ThreadLocal 本身不存值,它只是给 Thread 对象打了个「洞」,让每个线程都能往自己的口袋里存取数据。

  • 入口在 ThreadLocal,存储在 Thread.threadLocals
  • 每个线程都有自己的 Map,天然线程隔离
  • 使用黄金分割哈希 + 线性探测解决冲突
  • Entry 用弱引用存 key,但 value 仍需手动清理

相关链接

基于 VitePress 构建