💼 剧情回顾:线程家庭的“财政危机”
线程老公A偷偷把年终奖塞进ThreadLocal保险柜,老婆线程B翻遍堆内存也找不到!
线程老公C更狠——在保险柜里藏了前女友的微信,老婆线程D用get()
方法查岗时...空指针异常!
(画外音:这哪是保险柜?这简直是程序员的“舔狗对象寄存处”!)
🔒 ThreadLocal是什么?
它就像线程的私人保险柜:
每人一柜:每个线程打开ThreadLocal时,拿到的是自己独有的那份
钥匙唯一:只有当前线程能打开自己的柜子
藏啥都行:从数据库连接到用户登录信息,藏得悄无声息
代码版藏私房钱:
ThreadLocal<String> 私房钱 = new ThreadLocal<>();new Thread(() -> {私房钱.set("比特币100枚"); // 偷偷藏钱System.out.println("线程A的私房钱:" + 私房钱.get()); // 随时取用}).start();new Thread(() -> {私房钱.set("空的!");System.out.println("线程B看到的:" + 私房钱.get()); // 永远找不到别人的}).start();
输出:
线程A的私房钱:比特币100枚线程B看到的:空的!
🔧 原理解密:线程的隐秘小本本
每个线程身上都带着ThreadLocalMap小本本:
Key弱引用:ThreadLocal对象作为键(弱引用,防内存泄漏)
Value强引用:你存的值是强引用(这就是坑!)
哈希寻宝:通过哈希算法快速找到自己的保险箱位置
(画外音:当线程挂掉时——"临终遗言:快...快remove()我的黑历史!")
👨💻 手搓“低配版ThreadLocal”
用HashMap暴力实现(注意:真货比这高级多了!):
public class MyThreadLocal<T> {private Map<Thread, T> 保险柜 = new ConcurrentHashMap<>();public void set(T value) {保险柜.put(Thread.currentThread(), value);}public T get() {return 保险柜.get(Thread.currentThread());}public void remove() {保险柜.remove(Thread.currentThread());}}
缺陷警告:
真·ThreadLocal的Key是弱引用,这里是强引用
线程池中线程复用会导致Map爆炸(内存泄漏警告!)
💣 保险柜使用禁忌(避坑指南)
用完必须remove()!
否则线程池复用线程时,数据错乱+内存泄漏!try {threadLocal.set("敏感数据");// ...业务操作} finally {threadLocal.remove(); // 必须像擦屁股一样坚决!}别用非static的ThreadLocal
static修饰防止每次new实例导致内存泄漏:private static ThreadLocal<User> userHolder = new ThreadLocal<>();小心父子线程
子线程拿不到父线程的ThreadLocal值(需用InheritableThreadLocal
传家宝)别当全局变量垃圾桶
把ThreadLocal当万能全局变量用?——等着被同事祭天吧!
🎮 实战:用户登录信息传递
public class UserContext {private static ThreadLocal<User> holder = new ThreadLocal<>();// 登录后调用public static void setUser(User user) {holder.set(user);}// 任何地方获取public static User getUser() {return holder.get();}// 拦截器记得清理!public static void clear() {holder.remove();}}// 使用示例new Thread(() -> {UserContext.setUser(new User("张老三"));try {System.out.println("当前用户:" + UserContext.getUser()); // 张老三} finally {UserContext.clear(); // 必须擦屁股!}}).start();
(画外音:当你不remove()时——"用户A看到了用户B的隐私,删库跑路.gif")
🚨 血泪教训:内存泄漏惨案
ThreadLocal<byte[]> 内存炸弹 = new ThreadLocal<>();new Thread(() -> {内存炸弹.set(new byte[1024 * 1024 * 100]); // 100MB大对象// 忘记remove()!!!}).start();// 即使线程结束,ThreadLocalMap仍持有byte[]的强引用// GC来了也收不走,OOM正在路上...
灵魂拷问:你的项目里有没有这种“定时炸弹”?
👉 关注公众号【让天下没有难学的编程】

下期预告:《线程池:程序界的富士康——线程生死轮回的流水线!》
彩蛋:
当面试官问:“ThreadLocal为什么可能内存泄漏?”
你可以优雅回答:
“ThreadLocalMap的Key是弱引用,但Value是强引用。如果线程长期存活却不remove(),Value会一直强引用大对象,导致GC无法回收——所以,爱我你就remove()我~”




