ThreadLocal--ThreadLocal介绍
🧠 一、什么是
ThreadLocal
?
ThreadLocal
是 Java 提供的一种 线程本地变量机制;每个线程都维护一份自己的副本;
它不用于多个线程共享变量,而是用于每个线程独立维护自己的变量副本;
常用于:用户上下文、数据库连接、格式化对象(如
SimpleDateFormat
)、日志跟踪等场景。
🚀 二、
ThreadLocal
的基本用法ThreadLocal<String> local = new ThreadLocal<>(); local.set("hello"); // 设置当前线程的副本 String val = local.get(); // 获取当前线程的副本 local.remove(); // 手动删除,防止内存泄漏
每个线程访问的是 自己的变量副本,彼此隔离。
🧱 三、
Thread
、ThreadLocal
、ThreadLocalMap
三者关系图Thread (线程对象)└── ThreadLocalMap (每个线程独有的 map)└── Entry[] 数组├── key:ThreadLocal 对象(弱引用)└── value:真正的变量值
✅ 总结对应关系:
角色 说明 Thread
每个线程都有一个 ThreadLocalMap
ThreadLocal
作为 key 存在于 ThreadLocalMap
中,指向当前线程的副本ThreadLocalMap
是 Thread
内部的属性,负责存储每个ThreadLocal
对应的数据
🎯 四、为什么
ThreadLocalMap
的 key 是 弱引用?这个想要了解更详细可以看博主的另一篇博客:ThreadLocal--ThreadLocal 竟可能导致内存泄漏?看看 ThreadLocalMap 的弱引用机制-CSDN博客
✅ Java 源码:
static class Entry extends WeakReference<ThreadLocal<?>> {Object value; }
✅ 原因:防止内存泄漏(重点)
如果
ThreadLocal
是 强引用:
即使我们不再使用
ThreadLocal
,它依然会作为 key 强引用存在,永远不会被 GC;而且
ThreadLocalMap
属于Thread
,Thread
不结束就不会释放内存;久而久之,value 也无法回收,造成 内存泄漏。
✅ 如果是 弱引用:
当开发者不再持有
ThreadLocal
引用时,它会被 GC 回收;GC 后
ThreadLocalMap
中 key 为 null;如果调用
ThreadLocal.get()
/set()
,会清除掉这些 stale entry(陈旧数据);✅ 避免内存泄漏。
💣 五、ThreadLocal 内存泄漏陷阱
问题场景:
线程池中线程长时间不销毁;
ThreadLocal 被 GC 回收,但
ThreadLocalMap
的 value 还存在;如果不调用
.remove()
,value 永远不会清理;解决方式:
✅ 使用完后调用
ThreadLocal.remove()
清理;✅ 或者用
try-finally
包装使用逻辑:private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));public void parseDate(String dateStr) {try {formatter.get().parse(dateStr);} finally {formatter.remove(); // 手动清除,避免内存泄漏} }
🧰 六、ThreadLocalMap 的实现细节
本质上是一个自定义的哈希表:
数组结构 + 开放寻址法(冲突后线性探测)
不是
HashMap
,也不是ConcurrentHashMap
数组大小默认
16
,按需扩容(最多 2^30)
🛠 七、常用方法详解
方法 说明 set(T value)
设置当前线程副本中的值 get()
获取当前线程副本中的值 remove()
删除当前线程副本中的值 withInitial(Supplier)
构造带默认初始值的 ThreadLocal
✅ 示例:使用默认初始值的
ThreadLocal
ThreadLocal<Integer> counter = ThreadLocal.withInitial(() -> 0);public void increment() {counter.set(counter.get() + 1); }
🌟 八、
InheritableThreadLocal
:子线程继承父线程值InheritableThreadLocal<String> local = new InheritableThreadLocal<>(); local.set("父线程值");new Thread(() -> {System.out.println(local.get()); // 子线程能读取父线程设置的值 }).start();
适合:父线程传递上下文,如用户ID、请求ID 等。
🧭 九、实际应用场景
场景 示例 ✅ 用户上下文 登录后存放用户信息: ThreadLocal<User>
✅ DateFormat SimpleDateFormat
非线程安全,放入ThreadLocal
✅ 数据源切换 动态数据源管理,存放在 ThreadLocal
中✅ Trace ID 日志链路追踪,全链路唯一 ID 存 ThreadLocal ✅ Spring事务/安全 Spring 的 TransactionSynchronizationManager
、SecurityContextHolder
都用到了ThreadLocal
📌 十、总结
项目 内容 本质 每个线程一个变量副本 原理 每个线程有一个 ThreadLocalMap 结构 key 为弱引用的 ThreadLocal,value 为副本值 弱引用原因 防止内存泄漏,GC 后 key=null 自动清理 使用建议 用完及时调用 remove()
延伸功能 InheritableThreadLocal
实现值传递应用场景 用户信息、日期格式化、日志追踪、数据库连接等