当前位置: 首页 > article >正文

ThreadLocal数据结构、内存泄漏分析

文章目录

  • ⚽ThreadLocal
    • 🎉入门案例
    • 🎈ThreadLocal在线程中怎么存储的
    • 🎗为什么会造成内存泄漏?
    • 🎃ThreadLocalMap的key使用强引用和弱引用有什么区别呢?
    • 🔔补充说明
      • Java中引用类型分类
      • 内存泄漏和内存溢出区别

⚽ThreadLocal

用来为每个线程提供独立的变量副本的

🎉入门案例

使用案例

public class ThreadLocalMultipleExample {
    private static final ThreadLocal<Integer> threadLocal1 = ThreadLocal.withInitial(() -> 1);
    private static final ThreadLocal<String> threadLocal2 = ThreadLocal.withInitial(() -> "Hello");

    public static void main(String[] args) {
        // 线程A设置值
        Thread threadA = new Thread(() -> {
            threadLocal1.set(10);
            threadLocal2.set("Thread A");
            System.out.println("Thread A - local1: " + threadLocal1.get()); // 10
            System.out.println("Thread A - local2: " + threadLocal2.get()); // Thread A
        });

        // 线程B设置值
        Thread threadB = new Thread(() -> {
            System.out.println("Thread B - local1: " + threadLocal1.get()); // 1
            System.out.println("Thread B - local2: " + threadLocal2.get()); // Hello
        });

        threadA.start();
        threadB.start();
    }
}

运行结果:可以看到虽然线程A修改了变量的值,但是在线程B中变量的值还是初始给的值,因为这两个变量在每个线程中都有自己的副本

Thread A - local1: 10
Thread A - local2: Thread A
Thread B - local1: 1
Thread B - local2: Hello

🎈ThreadLocal在线程中怎么存储的

先来了解ThreadLocal在Thread时怎么存储的。

我们先看Thread的源码,里面有个ThreadLocalMap类型的变量。

public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

从下面ThreadLocal的部分源码中可以看出,这个ThreadLocalMap类是ThreadLocal类中静态内部类。我们可以把ThreadLocalMap当做一个Map,其中ThreadLocalkey,存储的值就是value

可以看到ThreadLocalset、get方法,都是用Thread.currentThread()获取当前线程后,拿到每个线程自己独有的ThreadLocalMap之后进行读写操作,所以这里保证了每个线程都有自己的ThreadLocal副本。

public class ThreadLocal<T> {

    // ......省略部分代码
    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }
    
    // ......省略部分代码
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
    // ......省略部分代码
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }
}

ThreadLocalMapHashMap在存储结构上有些不同,HashMap是数组+链表(+红黑树)的形式,但是ThreadLocalMap是纯数组的形式,内部只有一个Entry[] table数组,其中一个Entry就是一个键值对。

使用ThreadLocal时需要注意避免出现内存泄漏问题。

🎗为什么会造成内存泄漏?

我们从源码中可以看出ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有当thead线程退出以后,value的强引用链条才会断掉。意味着这个线程一直不结束的话,这个value就一直无法回收,造成内存泄漏,这种情况一般发生在使用线程池的场景中,因为里面的线程正常情况下会一直存活。

在平时使用ThreadLocal类时,要避免内存泄漏问题,可以在线程处理完任务后,使用threadLocal.remove()方法,移除当前threadLocal

🎃ThreadLocalMap的key使用强引用和弱引用有什么区别呢?

  • key 使用强引用:当ThreadLocalMap的key为强引用,发生GC时,因为ThreadLocalMap还持有ThreadLocal的强引用,同时ThreadLocalMap和Thread生命周期相同,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
  • key 使用弱引用:当ThreadLocalMap的key为弱引用,发生GC时,由于ThreadLocalMap持有的是ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次其他ThreadLocal调用他们的set(),get(),remove()方法的时候都会清除key为null对应的value值。

🔔补充说明

Java中引用类型分类

  • 强引用:我们常常 new 出来的对象就是强引用类型,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足的时候
  • 软引用:使用 SoftReference 修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收
  • 弱引用:使用 WeakReference 修饰的对象被称为弱引用,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收
  • 虚引用:虚引用是最弱的引用,在 Java 中使用 PhantomReference 进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知

内存泄漏和内存溢出区别

  • 内存泄漏 是因为程序没有释放不再使用的内存,导致内存逐渐积累,最终可能引起内存溢出。
  • 内存溢出 是当程序请求的内存超出了系统可分配的最大值时,操作系统无法满足内存请求,从而导致程序崩溃。

http://www.kler.cn/a/441103.html

相关文章:

  • 机器学习2 (笔记)(朴素贝叶斯,集成学习,KNN和matlab运用)
  • Linux——网络(tcp)
  • DeepSeek-R1本地部署笔记
  • java——继承
  • MySQL中的读锁与写锁:概念与作用深度剖析
  • 二进制安卓清单 binary AndroidManifest - XCTF apk 逆向-2
  • Maven 打包(system jar 和微服务父子项目)
  • ios系统冷知识
  • SamOutV2 0.18B模型发布
  • 接口测试常用工具 Postman
  • 基础开发工具-编辑器vim
  • PHPstudy中的数据库启动不了
  • Unity3D实现接口类的应用例子
  • STL 剖析
  • Docker:镜像操作(补充一)
  • 企业车辆管理系统(源码+数据库+报告)
  • 开源FreeSWITCH大模型智能客服系统的最佳实践
  • python 配置 oracle instant client
  • docker仓库数据传输加密
  • 通过解调使用正则化相位跟踪技术进行相位解包裹
  • java程序语言设计-反射加设计模式
  • 【使用PyQt5和YOLOv11开发电脑屏幕区域的实时分类GUI】——PyQt5在Pycharm中的安装配置
  • GCNet的简述
  • 解锁报表在线设计新高度:FastReport Online Designer 2025.1 正式上线!
  • 【C#】Debug和Release的区别和使用
  • 23. 合并 K 个升序链表(java)