连载中 13/21

ThreadLocal:每个线程的抽屉,与那桩著名的内存泄漏

2026-10-29 · 37 阅读 · 0 评论 · 0 赞

翻车:SimpleDateFormat 的日期错乱

多个线程共用一个 SimpleDateFormat 解析日期,输出偶尔错乱,甚至抛 NumberFormatException。原因:sdf 内部有共享的可变状态(Calendar),并发使用互相污染。修法三条:每次 new(浪费)、调用加锁(串行化)、ThreadLocal——每个线程发一个自己的 sdf,互不干扰。第三条是标准答案。

结构:数据存在 Thread 身上,不在 ThreadLocal 里

直觉以为 ThreadLocal 是个存值的容器,错。真相是:每个 Thread 对象内部自带一张 ThreadLocalMap,数据存在这张 map 里;ThreadLocal 对象本身只是访问地图的钥匙——调用 threadLocal.get(),等价于「拿到当前线程的 map,以这个 ThreadLocal 实例为 key 找值」。同样的代码,不同线程 get 到的是各自的值——因为 map 就长在各自的 Thread 身上。map 的结构还有个关键设计:key 是 ThreadLocal 的弱引用,value 是强引用。记住这个组合,下面的事故全由它引发。

内存泄漏:key 被回收,value 还赖着

key 用弱引用是有意为之:外部代码不再持有 ThreadLocal 实例时,下次 GC 就把它收走,key 变 null——ThreadLocal 对象本身不泄漏。但 value 是强引用,key 没了 value 还牢牢挂在 map 里,null key 的 entry 成了孤儿。更糟的是线程池:线程长期存活反复复用,孤儿 value 只进不出,泄漏被时间放大。ThreadLocalMap 有顺路自清理的逻辑(set、get 时探测清一些),但不保证及时。铁律:用完必须 remove(),写在 finally 里:

static final ThreadLocal<User> CONTEXT = new ThreadLocal<>();

try {
    CONTEXT.set(currentUser);
    // 业务逻辑,任意深处 CONTEXT.get()
} finally {
    CONTEXT.remove();   // 不写这行,线程池里就是定时炸弹
}

线程池下的「数据串台」

比泄漏更阴的坑是串台:线程池复用线程,上一个请求 set 的上下文没 remove,下一个请求 get 到的是别人的用户信息——标准的生产事故。串台和泄漏是同一个根源(value 残留),同一剂药方(finally remove)。顺带两句话:InheritableThreadLocal 能把父线程的值传给新建的子线程,但线程池里线程是复用的、不重新创建,不适用;跨线程池传递上下文的通用方案是阿里开源的 TransmittableThreadLocal,知道有这个东西即可。

典型应用与定位

三大常用场景:登录用户上下文(拦截器 set、业务 get、过滤器 finally remove)、数据库连接管理(一个线程一条连接)、SimpleDateFormat 一人一份。最后记住它的定位:ThreadLocal 不是让并发消失,而是让共享消失——没有共享,就没有竞争。它与前面所有的锁是两条独立的路线,不冲突,各管一摊。

显式锁与线程私有数据都齐了。下一篇进入全系列重头戏:线程池——七大参数、一个任务从提交到执行的完整旅程,以及为什么大厂规范明令禁止用 Executors 直接创建。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#Java#ThreadLocal#内存泄漏#JUC#并发编程

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10113 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9876 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9306 阅读 · 21 评论 · 287 赞