🧠 一、什么是
ThreadLocal?
ThreadLocal是 Java 提供的一种 线程本地变量机制;每个线程都维护一份自己的副本;
它不用于多个线程共享变量,而是用于每个线程独立维护自己的变量副本;
常用于:用户上下文、数据库连接、格式化对象(如
SimpleDateFormat)、日志跟踪等场景。
🚀 二、
ThreadLocal的基本用法
javaThreadLocal<String> local = new ThreadLocal<>(); local.set("hello"); // 设置当前线程的副本 String val = local.get(); // 获取当前线程的副本 local.remove(); // 手动删除,防止内存泄漏每个线程访问的是 自己的变量副本,彼此隔离。
🧱 三、
Thread、ThreadLocal、ThreadLocalMap三者关系图
diffThread (线程对象) └── ThreadLocalMap (每个线程独有的 map) └── Entry[] 数组 ├── key:ThreadLocal 对象(弱引用) └── value:真正的变量值✅ 总结对应关系:
角色 说明 Thread每个线程都有一个 ThreadLocalMapThreadLocal作为 key 存在于 ThreadLocalMap中,指向当前线程的副本ThreadLocalMap是 Thread内部的属性,负责存储每个ThreadLocal对应的数据
🎯 四、为什么
ThreadLocalMap的 key 是 弱引用?这个想要了解更详细可以看博主的另一篇博客: ThreadLocal--ThreadLocal 竟可能导致内存泄漏?看看 ThreadLocalMap 的弱引用机制-CSDN博客
✅ Java 源码:
javastatic 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包装使用逻辑:
javaprivate 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
javaThreadLocal<Integer> counter = ThreadLocal.withInitial(() -> 0); public void increment() { counter.set(counter.get() + 1); }
🌟 八、
InheritableThreadLocal:子线程继承父线程值
javaInheritableThreadLocal<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实现值传递应用场景 用户信息、日期格式化、日志追踪、数据库连接等
ThreadLocal--ThreadLocal介绍
你我约定有三2025-07-28 11:30