一、核心问题:为什么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
// ...
}
关键点:
Thread.currentThread()返回的是当前执行代码的线程对象- 每个线程对象都有自己独立的
threadLocals字段 - 所以每个线程访问的是自己的 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 核心原理
- ThreadLocal不存储数据:ThreadLocal只是一个"钥匙"
- 数据存储在Thread对象中 :每个Thread对象有
threadLocals字段 - ThreadLocalMap是实际存储:类似HashMap,但使用数组+开放地址法
- 第一次调用时创建 :调用
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是独立的,互不干扰的根本原因!