ThreadLocal底层实现原理深度解析

一、核心问题:为什么MDC.put会为线程创建ThreadLocal?

1.1 问题的本质

当我们调用 MDC.put("tid", "abc123") 时,底层发生了什么?

java 复制代码
// 我们看到的代码
MDC.put("tid", "abc123");

// 底层实际执行
ThreadLocal<Map<String, String>> contextMap = ...;
contextMap.set(new HashMap<>());  // 为当前线程创建ThreadLocal

关键点 :ThreadLocal本身不存储数据,数据存储在Thread对象中!

二、ThreadLocal的底层数据结构

2.1 Thread对象内部结构

java 复制代码
// Thread类的简化结构(JDK源码)
public class Thread implements Runnable {
    // 每个Thread对象都有一个ThreadLocalMap
    ThreadLocal.ThreadLocalMap threadLocals = null;
    
    // 用于InheritableThreadLocal(可继承的ThreadLocal)
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

关键理解

  • ThreadLocalMap 不是存储在 ThreadLocal 中,而是存储在 Thread 对象
  • 每个 Thread 对象都有自己的 ThreadLocalMap
  • ThreadLocal 只是一个"钥匙",用来访问 Thread 对象中的 Map

2.2 ThreadLocalMap的内部结构

java 复制代码
// ThreadLocalMap的简化实现
static class ThreadLocalMap {
    // 内部使用Entry数组存储,类似HashMap
    private Entry[] table;
    
    // Entry是弱引用,key是ThreadLocal实例,value是实际存储的值
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;  // 实际存储的值
        
        Entry(ThreadLocal<?> k, Object v) {
            super(k);  // ThreadLocal作为key,使用弱引用
            value = v;  // 实际值
        }
    }
}

内存结构图

ini 复制代码
Thread对象 (Thread-1)
  └─ threadLocals: ThreadLocalMap
      └─ table: Entry[]
          ├─ Entry[0]: key=MDC的ThreadLocal实例(弱引用), value=Map{"tid": "abc123"}
          ├─ Entry[1]: key=其他ThreadLocal实例(弱引用), value=其他值
          └─ Entry[2]: null

三、MDC.put的完整执行过程

3.1 代码追踪

java 复制代码
// ========== 第1步:调用MDC.put ==========
MDC.put("tid", "abc123");

// ========== 第2步:MDC内部实现 ==========
public class MDC {
    // MDC内部有一个静态的ThreadLocal实例
    private static ThreadLocal<Map<String, String>> contextMap = new ThreadLocal<>();
    
    public static void put(String key, String value) {
        // 2.1 获取当前线程的Map
        Map<String, String> map = contextMap.get();
        // 这里会触发ThreadLocal.get()方法
        
        // 2.2 如果Map不存在,创建新Map
        if (map == null) {
            map = new HashMap<>();
            contextMap.set(map);  // 这里会触发ThreadLocal.set()方法
        }
        
        // 2.3 存储key-value
        map.put(key, value);
    }
}

3.2 ThreadLocal.get()的底层实现

java 复制代码
// ThreadLocal.get()的完整实现(JDK源码简化版)
public T get() {
    // 1. 获取当前线程对象
    Thread t = Thread.currentThread();
    
    // 2. 获取线程对象中的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // getMap(t) 实际就是: return t.threadLocals;
    
    // 3. 如果Map存在,从Map中获取值
    if (map != null) {
        // 3.1 以当前ThreadLocal实例为key,查找Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        // this 是 MDC的ThreadLocal实例
        
        // 3.2 如果找到Entry,返回value
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
    
    // 4. 如果Map不存在或找不到Entry,返回null
    return setInitialValue();
}

执行流程图

csharp 复制代码
MDC.put("tid", "abc123")
  ↓
contextMap.get()
  ↓
Thread.currentThread()  → 获取当前线程对象
  ↓
t.threadLocals  → 获取线程的ThreadLocalMap
  ↓
如果threadLocals == null
  ↓
返回null(第一次调用时)
  ↓
contextMap.set(new HashMap<>())

3.3 ThreadLocal.set()的底层实现

java 复制代码
// ThreadLocal.set()的完整实现(JDK源码简化版)
public void set(T value) {
    // 1. 获取当前线程对象
    Thread t = Thread.currentThread();
    
    // 2. 获取线程对象中的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // getMap(t) 实际就是: return t.threadLocals;
    
    // 3. 如果Map存在,直接设置值
    if (map != null) {
        map.set(this, value);
        // this 是 MDC的ThreadLocal实例
        // value 是 new HashMap<>()
    } else {
        // 4. 如果Map不存在,创建新的ThreadLocalMap
        createMap(t, value);
        // createMap(t, value) 实际就是:
        //     t.threadLocals = new ThreadLocalMap(this, value);
    }
}

关键点

  • createMap(t, value) 会创建新的 ThreadLocalMap 并赋值给 t.threadLocals
  • 这就是"为线程创建ThreadLocal"的本质:在Thread对象中创建ThreadLocalMap

3.4 createMap的详细过程

java 复制代码
// ThreadLocal.createMap()的实现
void createMap(Thread t, T firstValue) {
    // 创建新的ThreadLocalMap,并赋值给线程的threadLocals字段
    t.threadLocals = new ThreadLocalMap(this, firstValue);
    // this 是 MDC的ThreadLocal实例
    // firstValue 是 new HashMap<>()
}

// ThreadLocalMap的构造函数
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    // 1. 创建Entry数组(初始容量16)
    table = new Entry[INITIAL_CAPACITY];
    
    // 2. 计算ThreadLocal实例的hash值,确定在数组中的位置
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    
    // 3. 创建Entry并存储到数组中
    table[i] = new Entry(firstKey, firstValue);
    // Entry存储:
    //   - key: MDC的ThreadLocal实例(弱引用)
    //   - value: new HashMap<>()
    
    size = 1;
}

四、完整的内存模型

4.1 第一次调用MDC.put时的内存状态

csharp 复制代码
┌─────────────────────────────────────────────────────────────────┐
│ 执行前:                                                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│ Thread-1对象                                                      │
│   └─ threadLocals: null  ← 还没有ThreadLocalMap                 │
│                                                                   │
│ MDC类(静态)                                                     │
│   └─ contextMap: ThreadLocal实例(所有线程共享这个实例)          │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 执行: MDC.put("tid", "abc123")                                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│ 第1步: contextMap.get()                                          │
│   ├─ Thread.currentThread() → Thread-1对象                       │
│   ├─ Thread-1.threadLocals → null                              │
│   └─ 返回null                                                    │
│                                                                   │
│ 第2步: contextMap.set(new HashMap<>())                           │
│   ├─ Thread.currentThread() → Thread-1对象                       │
│   ├─ Thread-1.threadLocals → null(不存在)                     │
│   ├─ createMap(Thread-1, new HashMap<>())                       │
│   │   └─ Thread-1.threadLocals = new ThreadLocalMap(...)       │
│   │       └─ table[0] = Entry(MDC的ThreadLocal实例, HashMap)    │
│   └─ 现在Thread-1有了ThreadLocalMap                             │
│                                                                   │
│ 第3步: map.put("tid", "abc123")                                 │
│   └─ 在HashMap中存储key-value                                    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 执行后:                                                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│ Thread-1对象                                                      │
│   └─ threadLocals: ThreadLocalMap                                │
│       └─ table: Entry[]                                          │
│           └─ Entry[0]:                                           │
│               ├─ key: MDC的ThreadLocal实例(弱引用)             │
│               └─ value: HashMap{"tid": "abc123"}                 │
│                                                                   │
│ Thread-2对象(另一个线程)                                       │
│   └─ threadLocals: null  ← 还没有调用MDC.put                    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

4.2 第二次调用MDC.put时的内存状态

css 复制代码
┌─────────────────────────────────────────────────────────────────┐
│ 执行: MDC.put("EagleEye-TraceID", "abc123")                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│ 第1步: contextMap.get()                                          │
│   ├─ Thread.currentThread() → Thread-1对象                       │
│   ├─ Thread-1.threadLocals → ThreadLocalMap(已存在)            │
│   ├─ map.getEntry(MDC的ThreadLocal实例)                         │
│   │   └─ 在table中找到Entry[0]                                  │
│   └─ 返回: HashMap{"tid": "abc123"}                             │
│                                                                   │
│ 第2步: map.put("EagleEye-TraceID", "abc123")                    │
│   └─ 在已有的HashMap中添加新的key-value                          │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ 执行后:                                                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│ Thread-1对象                                                      │
│   └─ threadLocals: ThreadLocalMap(同一个Map)                   │
│       └─ table: Entry[]                                          │
│           └─ Entry[0]:                                           │
│               ├─ key: MDC的ThreadLocal实例(弱引用)             │
│               └─ value: HashMap{                                 │
│                     "tid": "abc123",                             │
│                     "EagleEye-TraceID": "abc123"                 │
│                   }                                               │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

五、ThreadLocalMap的哈希算法

5.1 为什么使用数组而不是HashMap?

ThreadLocalMap使用数组+开放地址法,而不是HashMap的数组+链表/红黑树:

java 复制代码
// ThreadLocalMap的哈希算法
private int getIndex(ThreadLocal<?> key) {
    // 1. 获取ThreadLocal实例的hashCode
    int hashCode = key.threadLocalHashCode;
    
    // 2. 计算数组索引(取模运算)
    int index = hashCode & (table.length - 1);
    // 等价于: index = hashCode % table.length
    // 但位运算更快
    
    return index;
}

为什么使用开放地址法?

  • ThreadLocal的数量通常很少(每个线程可能只有几个ThreadLocal)
  • 开放地址法在数据量小时性能更好,内存占用更少
  • 避免链表/红黑树的额外内存开销

5.2 ThreadLocal的hashCode生成

java 复制代码
// ThreadLocal类中
public class ThreadLocal<T> {
    // 每个ThreadLocal实例都有一个唯一的hashCode
    private final int threadLocalHashCode = nextHashCode();
    
    // 静态计数器,每次创建ThreadLocal实例时递增
    private static AtomicInteger nextHashCode = new AtomicInteger();
    
    // 每次递增的值(黄金比例相关,用于更好的哈希分布)
    private static final int HASH_INCREMENT = 0x61c88647;
    
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
}

示例

java 复制代码
// 创建第一个ThreadLocal
ThreadLocal<Map> contextMap = new ThreadLocal<>();
// threadLocalHashCode = 0

// 创建第二个ThreadLocal
ThreadLocal<String> otherMap = new ThreadLocal<>();
// threadLocalHashCode = 0x61c88647

// 创建第三个ThreadLocal
ThreadLocal<Integer> anotherMap = new ThreadLocal<>();
// threadLocalHashCode = 0x61c88647 * 2

六、弱引用的作用

6.1 Entry使用弱引用的原因

java 复制代码
static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    
    Entry(ThreadLocal<?> k, Object v) {
        super(k);  // ThreadLocal作为key,使用弱引用
        value = v;
    }
}

为什么key使用弱引用?

问题场景

java 复制代码
// 如果key使用强引用
public void method() {
    ThreadLocal<Map> local = new ThreadLocal<>();
    local.set(new HashMap<>());
    // local变量超出作用域,但ThreadLocalMap中的Entry仍然持有强引用
    // ThreadLocal对象无法被GC回收 → 内存泄漏
}

// 如果key使用弱引用
public void method() {
    ThreadLocal<Map> local = new ThreadLocal<>();
    local.set(new HashMap<>());
    // local变量超出作用域
    // ThreadLocal对象可以被GC回收(因为只有弱引用)
    // 但value仍然存在 → 需要手动清理
}

内存泄漏风险

css 复制代码
Thread-1.threadLocals
  └─ Entry[0]:
      ├─ key: ThreadLocal实例(弱引用)← 可以被GC回收
      └─ value: HashMap(强引用)← 如果ThreadLocal被回收,这个value就"泄漏"了

解决方案

  • ThreadLocalMap在get/set时会清理key为null的Entry(stale entries)
  • 但最好的做法是:使用完后调用remove()或clear()

七、完整执行流程示例

7.1 第一次调用MDC.put的完整过程

java 复制代码
// ========== 代码调用 ==========
MDC.put("tid", "abc123");

// ========== 第1步:MDC.put()方法 ==========
public static void put(String key, String value) {
    Map<String, String> map = contextMap.get();
    // 调用ThreadLocal.get()
}

// ========== 第2步:ThreadLocal.get() ==========
public T get() {
    Thread t = Thread.currentThread();
    // t = Thread-http-nio-8001-exec-1对象
    
    ThreadLocalMap map = getMap(t);
    // getMap(t) = t.threadLocals
    // 此时 t.threadLocals = null(第一次调用)
    
    if (map != null) {
        // 不执行(map为null)
    }
    
    return setInitialValue();
    // 调用setInitialValue()
}

// ========== 第3步:ThreadLocal.setInitialValue() ==========
private T setInitialValue() {
    T value = initialValue();
    // initialValue()返回null(ThreadLocal的默认实现)
    
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // map = null
    
    if (map != null) {
        // 不执行
    } else {
        createMap(t, value);
        // 创建ThreadLocalMap
    }
    
    return value;  // 返回null
}

// ========== 第4步:ThreadLocal.createMap() ==========
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
    // this = MDC的ThreadLocal实例
    // firstValue = null(但后面会重新set)
}

// ========== 第5步:ThreadLocalMap构造函数 ==========
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];  // 创建16大小的数组
    
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // 计算哈希索引,假设 i = 3
    
    table[i] = new Entry(firstKey, firstValue);
    // table[3] = Entry(MDC的ThreadLocal实例, null)
    
    size = 1;
}

// ========== 第6步:回到MDC.put() ==========
Map<String, String> map = contextMap.get();
// map = null(因为setInitialValue返回null)

if (map == null) {
    map = new HashMap<>();
    contextMap.set(map);
    // 调用ThreadLocal.set()
}

// ========== 第7步:ThreadLocal.set() ==========
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // map = ThreadLocalMap(已存在)
    
    if (map != null) {
        map.set(this, value);
        // 在ThreadLocalMap中设置值
    }
}

// ========== 第8步:ThreadLocalMap.set() ==========
private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len - 1);
    // 计算索引,假设 i = 3
    
    // 查找Entry(处理哈希冲突)
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        
        if (k == key) {
            // 找到对应的Entry,更新value
            e.value = value;
            return;
        }
        
        if (k == null) {
            // key被GC回收了,替换这个stale entry
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    
    // 没找到,创建新Entry
    tab[i] = new Entry(key, value);
    // table[3].value = new HashMap<>()
    
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold) {
        rehash();  // 扩容
    }
}

// ========== 第9步:回到MDC.put() ==========
map.put("tid", "abc123");
// 在HashMap中存储key-value

7.2 内存状态变化

ini 复制代码
执行前:
Thread-1.threadLocals = null

执行createMap后:
Thread-1.threadLocals = ThreadLocalMap {
    table[3] = Entry(MDC的ThreadLocal实例, null)
}

执行set后:
Thread-1.threadLocals = ThreadLocalMap {
    table[3] = Entry(MDC的ThreadLocal实例, HashMap{})
}

执行map.put后:
Thread-1.threadLocals = ThreadLocalMap {
    table[3] = Entry(MDC的ThreadLocal实例, HashMap{"tid": "abc123"})
}

八、为什么每个线程的ThreadLocal是独立的?

8.1 关键机制

java 复制代码
// ThreadLocal.get()的实现
public T get() {
    Thread t = Thread.currentThread();  // ← 关键:获取当前线程
    ThreadLocalMap map = getMap(t);     // ← 关键:从当前线程获取Map
    // ...
}

关键点

  1. Thread.currentThread() 返回的是当前执行代码的线程对象
  2. 每个线程对象都有自己独立的 threadLocals 字段
  3. 所以每个线程访问的是自己的 ThreadLocalMap

8.2 多线程示例

java 复制代码
// 线程1执行
Thread-1: 
  Thread.currentThread() → Thread-1对象
  Thread-1.threadLocals → Thread-1的ThreadLocalMap
  MDC.get("tid") → 从Thread-1的Map读取

// 线程2执行(同时)
Thread-2:
  Thread.currentThread() → Thread-2对象
  Thread-2.threadLocals → Thread-2的ThreadLocalMap
  MDC.get("tid") → 从Thread-2的Map读取

// 两个线程互不干扰

九、总结

9.1 核心原理

  1. ThreadLocal不存储数据:ThreadLocal只是一个"钥匙"
  2. 数据存储在Thread对象中 :每个Thread对象有threadLocals字段
  3. ThreadLocalMap是实际存储:类似HashMap,但使用数组+开放地址法
  4. 第一次调用时创建 :调用set()时,如果Thread的threadLocals为null,会创建新的ThreadLocalMap

9.2 执行流程

scss 复制代码
MDC.put("tid", "abc123")
  ↓
ThreadLocal.get()
  ↓
Thread.currentThread() → 获取当前线程对象
  ↓
t.threadLocals → 获取线程的ThreadLocalMap(可能为null)
  ↓
如果为null → createMap() → 创建ThreadLocalMap并赋值给t.threadLocals
  ↓
ThreadLocal.set() → 在ThreadLocalMap中存储值
  ↓
Thread-1.threadLocals.table[i] = Entry(ThreadLocal实例, HashMap)

9.3 关键理解

"为线程创建ThreadLocal"的本质

  • 不是在ThreadLocal中创建数据
  • 而是在Thread对象中创建ThreadLocalMap
  • ThreadLocal只是用来访问Thread对象中数据的"钥匙"

内存模型

ini 复制代码
Thread对象
  └─ threadLocals: ThreadLocalMap(每个线程独立)
      └─ table: Entry[](存储实际数据)
          └─ Entry: key=ThreadLocal实例, value=实际值

这就是为什么每个线程的MDC是独立的,互不干扰的根本原因!

相关推荐
lkbhua莱克瓦242 小时前
Java进阶——集合进阶(MAP)
java·开发语言·笔记·github·学习方法·map
spionbo3 小时前
在使用Windows系统,特别是Windows Update更新你的系统时,可能会遇到错误代码0x80070422。
github
HelloGitHub3 小时前
节省 60% Token 的新数据格式「GitHub 热点速览」
开源·github
全干engineer15 小时前
idea拉取github代码 -TLS connect error 异常问题
java·github·intellij-idea
Andya_net16 小时前
网络安全 | 深入了解OAuth 2.0原理
安全·web安全·github
小鱼小鱼.oO17 小时前
Claude Code 功能+技巧
github·aigc·claude code
spionbo18 小时前
Win11最新开发版开源工具新版发布,蓝屏、死机原因、内存诊断功能都有,教程来了
github
海域云赵从友19 小时前
2025年印尼服务器选型指南:跨境业务落地的合规与性能双解
人工智能·git·github
不会写代码的里奇20 小时前
VMware Ubuntu 22.04 NAT模式下配置GitHub SSH完整教程(含踩坑实录+报错_成功信息对照)
linux·经验分享·笔记·git·ubuntu·ssh·github