暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

【惊天秘密】ThreadLocal:每个线程都在偷偷藏私房钱?老婆线程永远找不到!

💼 剧情回顾:线程家庭的“财政危机”

线程老公A偷偷把年终奖塞进ThreadLocal保险柜,老婆线程B翻遍堆内存也找不到!
线程老公C更狠——在保险柜里藏了前女友的微信,老婆线程D用get()
方法查岗时...空指针异常

(画外音:这哪是保险柜?这简直是程序员的“舔狗对象寄存处”!)


🔒 ThreadLocal是什么?

它就像线程的私人保险柜

  1. 每人一柜:每个线程打开ThreadLocal时,拿到的是自己独有的那份

  2. 钥匙唯一:只有当前线程能打开自己的柜子

  3. 藏啥都行:从数据库连接到用户登录信息,藏得悄无声息

代码版藏私房钱

    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小本本:

      1. Key弱引用:ThreadLocal对象作为键(弱引用,防内存泄漏)

      2. Value强引用:你存的值是强引用(这就是坑!)

      3. 哈希寻宝:通过哈希算法快速找到自己的保险箱位置

      (画外音:当线程挂掉时——"临终遗言:快...快remove()我的黑历史!")


      👨💻 手搓“低配版ThreadLocal”

      用HashMap暴力实现(注意:真货比这高级多了!):

        public class MyThreadLocal<T> {
            private Map<Thread, T> 保险柜 = new ConcurrentHashMap<>();
            public void set(value) {
                保险柜.put(Thread.currentThread(), value);
            }
            public T get() {
                return 保险柜.get(Thread.currentThread());
            }
            public void remove() {
                保险柜.remove(Thread.currentThread());
            }
        }

        缺陷警告

        • 真·ThreadLocal的Key是弱引用,这里是强引用

        • 线程池中线程复用会导致Map爆炸(内存泄漏警告!)


        💣 保险柜使用禁忌(避坑指南)

        1. 用完必须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()我~”


                文章转载自让天下没有难学的编程,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                评论