ThreadLocal介绍
目录
ThreadLocal
一、场景引入
二、核心底层原理
基础结构说明
set () /get () 完整执行流程
线程隔离本质
三、代码示例
四、重要注意事项
五、生活化类比
ThreadLocal
一、场景引入
Tomcat 依靠线程池处理客户端请求,每一个用户请求都会分配线程池中的一条工作线程执行,请求处理完成后线程回收复用。由于每条请求由独立线程执行,我们可以借助 ThreadLocal 实现线程数据隔离,让每条线程只操作属于自身的数据,常用来存储登录用户信息、请求上下文、链路追踪 ID 等数据。
二、核心底层原理
基础结构说明
每个 Thread 线程对象内部自带专属容器 ThreadLocalMap,该容器归当前线程独有;多个不同的 ThreadLocal 实例,可以共用当前线程这一张 ThreadLocalMap 完成数据存取。区分:ThreadLocal 只是提供存取数据的操作工具,并不是存储数据的容器,真正存储数据的载体是线程内部的 ThreadLocalMap。
set () /get () 完整执行流程
set (value):获取当前执行线程 → 获取该线程独有的 ThreadLocalMap → 以当前 ThreadLocal 实例作为 key,存入 value。get ():获取当前执行线程 → 获取线程的 ThreadLocalMap → 使用 ThreadLocal 实例作为 key 查询对应数据。
线程隔离本质
多个线程共用同一个全局 ThreadLocal 静态对象操作数据时,数据分别保存在各自线程内部的 ThreadLocalMap 中,天然互不干扰,以此实现线程隔离。
📌 常见误区澄清(重点补充)静态的 ThreadLocal 实例是全局唯一,所有线程共享同一个 ThreadLocal 对象。很多人会产生疑问:多个线程共用同一个 key,CPU 时间片切换交替执行,会不会出现数据相互覆盖?原理解答:传统认知中「同一个 Map 内 key 重复会覆盖 value」这条规则依旧成立,但是每条线程拥有独立的 ThreadLocalMap。共用的 ThreadLocal 只是 key 对象,但是存取操作发生在不同线程相互隔离的 Map 容器中,跨 Map 不存在 key 冲突、数据覆盖问题。通俗理解:相当于同一把钥匙,去不同房间各自独立的储物柜存放物品,互不干扰。补充设计思考:不推荐每个线程单独 new ThreadLocal,如果每个线程创建独立 ThreadLocal 实例(不同的 key),就失去了统一的数据访问入口,违背 ThreadLocal 设计目标。
三、代码示例
public class ThreadLocalDemo { // 定义全局静态ThreadLocal对象,多个线程共用这同一个对象 private static final ThreadLocal<String> contextTl = new ThreadLocal<>(); public static void main(String[] args) { // 线程1 new Thread(() -> { contextTl.set("用户A的上下文信息"); System.out.println(Thread.currentThread().getName() + ":" + contextTl.get()); // 使用完毕清除数据 contextTl.remove(); }, "线程1").start(); // 线程2 new Thread(() -> { contextTl.set("用户B的上下文信息"); System.out.println(Thread.currentThread().getName() + ":" + contextTl.get()); contextTl.remove(); }, "线程2").start(); } }运行结果:
线程1:用户A的上下文信息 线程2:用户B的上下文信息四、重要注意事项
- static final 全局 ThreadLocal(项目标准用法,存储请求上下文、登录信息) 静态变量长期持有强引用,Entry 内弱引用 key 无法被 GC 回收,JVM 自动清理机制失效;任务结束必须手动 remove (),不能指望 GC 自动释放。
- 方法内局部 ThreadLocal(极少业务场景)方法运行结束后强引用消失,GC 可以回收 ThreadLocal 对象,触发 Map 自动清理过期 Entry;但清理时机完全不可控,依然不建议依赖该机制。
