ThreadLocal 原理 概述 ThreadLocal 提供线程本地变量 ,每个线程都有自己独立的变量副本,实现线程间数据隔离。
核心数据结构 1 2 3 4 Thread └── ThreadLocalMap threadLocals └── Entry[] table └── Entry(ThreadLocal<?> key, Object value)
组件
说明
Thread.threadLocals
每个线程持有的 ThreadLocalMap 实例
ThreadLocalMap
自定义哈希表,存储线程本地变量
Entry
继承 WeakReference<ThreadLocal<?>> 的键值对
工作原理 1. set() 方法 1 2 3 4 5 6 7 8 9 public void set (T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) { map.set(this , value); } else { createMap(t, value); } }
2. get() 方法 1 2 3 4 5 6 7 8 9 10 11 public T get () { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) { Entry e = map.getEntry(this ); if (e != null ) { return (T) e.value; } } return setInitialValue(); }
3. 数据存储位置 graph LR
A[ThreadLocal.get/set] --> B[Thread.currentThread]
B --> C[thread.threadLocals]
C --> D[ThreadLocalMap]
D --> E[Entry数组]
E --> F["key=ThreadLocal实例<br/>value=线程本地值"]
关键设计 Entry 使用弱引用 1 2 3 4 5 6 7 static class Entry extends WeakReference <ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super (k); value = v; } }
为什么 key 用弱引用? 👉Java引用类型详解 ^weak-ref-design
当外部不再持有 ThreadLocal 引用时,GC 可以回收 ThreadLocal 对象
避免 ThreadLocal 对象本身的内存泄漏
哈希冲突处理 ThreadLocalMap 使用线性探测法 解决哈希冲突:
1 2 3 4 5 6 7 8 9 10 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 被回收(弱引用)
Entry.key = null
线程仍存活(如线程池)
Entry.value 无法回收
解决方案 始终在使用后调用 remove() :
1 2 3 4 5 6 7 8 ThreadLocal<User> userContext = new ThreadLocal <>();try { userContext.set(currentUser); } finally { userContext.remove(); }
典型应用场景
场景
示例
用户上下文
存储当前登录用户信息
数据库连接
保证同一线程使用同一连接
事务管理
Spring 的事务同步
日期格式化
SimpleDateFormat 线程安全使用
链路追踪
TraceId 在线程内传递
与同步机制对比
特性
ThreadLocal
synchronized/Lock
原理
空间换时间,每个线程一份副本
时间换空间,排队访问
性能
无竞争,性能高
有锁竞争开销
适用场景
线程独享数据
线程共享数据
总结
存储位置 :数据存在每个线程的 threadLocals 字段中
数据结构 :ThreadLocalMap 是自定义哈希表,Entry 的 key 是弱引用
核心思想 :用空间换时间,避免同步开销
注意事项 :使用后必须调用 remove() 防止内存泄漏