源码:java.lang.ThreadLocal<T>.ThreadLocalMap 源码解析

1. TODO


2. 脑图

  1. Xmind

  2. Edraw

  3. Hexo 地址
    👉 http://blog.wangjia.ink/2025/11/16/源码:java.lang.ThreadLocal<T>.ThreadLocalMap源码解析/


3. 基础部分

3.1. ThreadLocal.ThreadLocalMap 概述

ThreadLocal.ThreadLocalMap 是一个具体类,是 ThreadLocal 的静态内部类

ThreadLocal.ThreadLocalMap 是通过 ThreadLocal.ThreadLocalMap->table 存储数据的,该数组是由 Entry 构建的数组。每个 Entrykey 必须是 ThreadLocal 类型,而 valueObject 类型。因此,我们可以把它简单理解为:

[!NOTE] 注意事项

  1. 猴哥的烦恼箱 (。•́︿•̀。)
    1. 为什么 ThreadLocal.ThreadLocalMap 要使用 ThreadLocal 实例作为 Key?能不能使用其他类的实例?
      1. 在一个复杂的 Java 应用中,会引入大量第三方依赖,它们内部可能都会使用 ThreadLocal 存储上下文数据
      2. 如果 Key 使用字符串(例如 “myName”),不同依赖可能恰好用到同名的 Key,最终导致数据被覆盖
      3. 而使用 ThreadLocal 实例作为 Key 依赖于它的对象地址,因为你即便创建了同名的 ThreadLocal 实例,它们的对象地址仍然是不同的
      4. 那么到底能不能使用其他类的实例呢?从实现的角度来看,只要是实例,就都能当 Key,当然不限制必须是 ThreadLocal 实例。但是 Entry 的构造方法被设计的就是使用 ThreadLocal 实例,所以我们就使用这个好了
    2. 为什么 Thread 不直接使用其他的 Map,而是使用 ThreadLocal.ThreadLocalMap
      1. 这主要是为了尽量避免内存泄漏,虽然没有彻底杜绝,但是比其他的 Map 要好得多
      2. 因为其他的 MapKey 是强引用,只要线程存活,那么 ThreadLocal 实例就永远无法被回收
      3. ThreadLocal.ThreadLocalMapKey 是弱引用,可以在 ThreadLocal 实例不再被使用时被回收
      4. 虽然 ThreadLocal#setThreadLocal#get 能在 Keynull 时清理 Value,但是这是 “被动清理”,并不可靠
    3. ThreadLocal.ThreadLocalMap 有没有位置冲突?它是如何解决位置冲突的?
      1. 有,它是采取 “线性探测” 的方式解决位置冲突的。即 如果 i 已经被占用,就尝试 i + 1,以此类推…

3.2. ThreadLocal.ThreadLocalMap 使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ThreadLocalDemo {

// 创建一个 ThreadLocal 实例,通常由 static final 修饰
private static final ThreadLocal<String> userContext = new ThreadLocal<>();

public static void main(String[] args) {

new Thread(() -> {
// 存数据
userContext.set("用户A的数据");
// 取数据
System.out.println(Thread.currentThread().getName() + " 获取: " + userContext.get());
// 删数据
userContext.remove();
}, "Thread-A").start();

// 模拟线程 B
new Thread(() -> {
userContext.set("用户B的数据");
System.out.println(Thread.currentThread().getName() + " 获取: " + userContext.get());
userContext.remove();
}, "Thread-B").start();
}
}

[!NOTE] 注意事项

  1. 在上述代码中,看起来像是在对 ThreadLocal 实例本身进行读写,但真正的逻辑是:通过当前线程找到它所持有的 ThreadLocal.ThreadLocalMap,然后在这个 MapEntry[] 中,以当前 ThreadLocal 实例为 Key 进行读写。
  2. 猴哥的烦恼箱 (。•́︿•̀。)
    1. 为什么 ThreadLocal 实例通常由 static final 修饰?
      1. 这主要是为了减少内存占用,因为即便多个 ThreadLocal.ThreadLocalMap 使用同一个 ThreadLocal 实例当 Key,但是它们互不影响。所以没必要专门为每一个 ThreadLocal.ThreadLocalMap 创建一个 ThreadLocal 实例

3.3. ThreadLocal.ThreadLocalMap 问题爆破

3.3.1. 数据 “脏读”

某线程在处理上一个任务时,向 ThreadLocal 写入了数据,但是任务结束后没有清理。当该线程去处理下一个任务的时候,调用 ThreadLocal#get 会读到上一个任务遗留下来的数据

为了解决这一问题,我们需要任务结束后调用 Thread#remove 删除数据


3.3.2. 内存泄漏

由于 ThreadLocal.ThreadLocalMapKey 是弱引用,而 Value 是强引用。所以当 ThreadLocal 实例被回收后,Key 会变成 null,但是对应的 Value 仍然存在,无法被回收

为了解决这一问题,我们需要任务结束后调用 Thread#remove 删除数据


4. 内部类

4.1. Entry

1
2
3
4
5
6
7
8
9
10
static class Entry extends WeakReference<ThreadLocal<?>> {

Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}

}

5. 核心属性

1
2
3
4
5
6
7
private static final int INITIAL_CAPACITY = 16;

private Entry[] table;

private int size = 0;

private int threshold;


源码:java.lang.ThreadLocal<T>.ThreadLocalMap 源码解析
https://wangjia5289.github.io/2025/11/16/源码:java.lang.ThreadLocal<T>.ThreadLocalMap源码解析/
Author
咸阳猴🐒
Posted on
November 16, 2025
Licensed under