面试高频题:ThreadLocal 是 private static final,全局唯一,每个线程持有独立的副本。这句话到底什么意思? 一、先拆解这句话privatestaticfinalThreadLocalStringthreadLocalnewThreadLocal();这句话其实包含两层含义分别由不同的修饰词负责修饰作用管的是什么static final全局只有一个 ThreadLocal 对象管的是钥匙key只有一把ThreadLocal每个线程存取的值互相隔离管的是柜子里的东西value各自独立隔离能力是 ThreadLocal 给的不是 static final 给的。static final只是保证这个 ThreadLocal 对象本身是单例避免创建多把没必要的钥匙。二、生活比喻 把 ThreadLocal 想象成一个储物柜编号比如 101 号储物柜编号是全局唯一的所有人都知道是 101 号但每个人线程去 101 号柜子里放/取东西时拿到的是自己那个柜子里的东西张三放了钥匙李四放了钱包互不影响三、代码验证publicclassThreadLocalDemo{// 全局唯一的 ThreadLocal 变量privatestaticfinalThreadLocalStringthreadLocalnewThreadLocal();publicstaticvoidmain(String[]args){newThread(()-{threadLocal.set(我是线程A的值);try{Thread.sleep(100);}catch(InterruptedExceptione){}System.out.println(线程A读到: threadLocal.get());// 我是线程A的值},Thread-A).start();newThread(()-{threadLocal.set(我是线程B的值);System.out.println(线程B读到: threadLocal.get());// 我是线程B的值},Thread-B).start();}}输出线程B读到: 我是线程B的值 线程A读到: 我是线程A的值虽然threadLocal只有一个对象但两个线程各自set、get的值完全隔离互不干扰。四、底层原理值到底存在哪4.1 核心结论值不是存在 ThreadLocal 对象里的而是存在每个 Thread 对象里的。ThreadLocal 对象全局唯一 │ │ 只是一个 key ▼ ┌─────────────────────────────────────────────┐ │ Thread-A 对象 │ │ └─ ThreadLocalMap │ │ └─ key: threadLocal → value: AAA │ ├─────────────────────────────────────────────┤ │ Thread-B 对象 │ │ └─ ThreadLocalMap │ │ └─ key: threadLocal → value: BBB │ ├─────────────────────────────────────────────┤ │ Thread-C 对象 │ │ └─ ThreadLocalMap │ │ └─ key: threadLocal → value: CCC │ └─────────────────────────────────────────────┘4.2 源码简化调用threadLocal.get()时实际执行的是publicTget(){ThreadtThread.currentThread();// 1. 拿到当前线程ThreadLocalMapmapt.threadLocals;// 2. 拿到当前线程的 MapEntryemap.getEntry(this);// 3. 用 thisThreadLocal作为 key 去查return(T)e.value;// 4. 返回当前线程存的值}4.3 各组件角色组件数量角色ThreadLocal 对象1 个static final充当 Map 的 keyThread 对象N 个每个线程一个各自持有一个 ThreadLocalMap实际的值N 份分别存在各线程的 Map 里五、如果去掉修饰词会怎样去掉 ThreadLocal换成普通 static final// ❌ 所有线程共享同一个值没有隔离privatestaticfinalStringvalue共享的;去掉 static final保留 ThreadLocal// ⚠️ 依然能隔离线程但可能创建多个 ThreadLocal 对象多把钥匙浪费且易出错privateThreadLocalStringthreadLocalnewThreadLocal();正确写法// ✅ 钥匙只有一把每个线程的值依然隔离privatestaticfinalThreadLocalStringthreadLocalnewThreadLocal();六、典型应用场景场景线程不安全的 SimpleDateFormat// ❌ 错误SimpleDateFormat 不是线程安全的多线程共享会出错privatestaticfinalSimpleDateFormatsdfnewSimpleDateFormat(yyyy-MM-dd);// ✅ 正确每个线程一份实例互不干扰privatestaticfinalThreadLocalSimpleDateFormatsdfThreadLocal.withInitial(()-newSimpleDateFormat(yyyy-MM-dd));// 使用StringdateStrsdf.get().format(newDate());其他常见场景数据库 Connection 绑定Spring 中的 RequestContextHolder请求上下文分布式链路追踪 TraceId用户登录信息透传七、注意事项 ⚠️内存泄漏线程池中的线程不会销毁ThreadLocalMap 中的 Entry 会一直存在。// ✅ 用完必须 removetry{threadLocal.set(value);// 业务逻辑...}finally{threadLocal.remove();// 防止内存泄漏}线程池复用问题线程被复用时上一个任务的值可能残留必须在任务开始时重新 set 或在结束时 remove。八、一句话总结static final→ 保证 ThreadLocal 这个引用/钥匙全局只有一个ThreadLocal→ 保证每个线程通过这把钥匙存取的值是隔离的ThreadLocal 本身不存数据它只是一把钥匙 真正的数据锁在每个线程自己的柜子Thread.threadLocals里。如果这篇文章对你有帮助欢迎点赞收藏