当前位置: 首页 > 科技观察

ThreadLocal真的会导致内存泄漏吗?

时间:2023-03-13 12:00:40 科技观察

ThreadLoca在并发场景下有很多应用。前几天有朋友问我一个问题,ThreadLocal是否真的会造成内存泄露?我今天将与您分享。一、ThreadLocal的基本原理考虑到很多朋友可能对ThreadLocal不是很了解,我先简单介绍一下ThreadLocal。在多个线程并发访问同一个共享变量的情况下,如果不进行同步控制,可能会造成数据不一致。因此,我们需要使用同步锁来解决。而ThreadLocal改变了一种处理多线程情况的思路。ThreadLocal本身不存储数据。它在线程中使用threadLocals属性。threadLocals的类型是ThreadLocal中定义的ThreadLocalMap对象。在调用ThreadLocal的set(Tvalue)方法时,ThreadLocal使用自己的引用,也就是this,作为Key,然后将用户传入的值作为Value存储在线程的ThreadLocalMap中,也就是说读取和读取每个线程的写操作都是基于线程自身的私有副本,线程之间的数据相互隔离。互不影响。这样,基于ThreadLocal的操作就不存在线程安全问题了。相当于采用了以空间换取时间的思想,从而提高了程序的执行效率。2、四种对象引用在ThreadLocalMap内部,维护了一个Entry数组表属性,用于存储键值对的映射关系。让我们看一下这个代码片段:staticclassThreadLocalMap{...privateEntry[]table;静态类Entry实现WeakReference>{对象值;条目(ThreadLocal<?>k,对象v){超级(k);值=v;}}...}Entry以ThreadLocal为Key,value为Value保存,继承自WeakReference,注意构造函数中第一行代码super(k),表示ThreadLocal对象是“弱引用”.有些朋友可能对“弱引用”并不熟悉。这里再介绍一下Java中的四种引用关系。JDK1.2之后,Java对引用的概念做了一些扩展,将引用分为“强”、“软”、“弱”、“虚”四种类型,从强到弱依次为:强引用:引用代码中无处不在的赋值行为,比如:Objecto=newObject(),只要强引用关系还在,对象就永远不会被回收。软引用:还是有用的,但不是必须存活的对象。JVM会在内存溢出之前回收它,比如:缓存。弱引用:不必生存的对象。引用关系弱于软引用。不管内存够不够,下次GC肯定会回收。幻象引用:又称“幽灵引用”、“幽灵引用”,最弱的引用关系,完全不影响对象的回收,相当于没有引用。幻象引用的唯一目的是在回收对象时接收系统通知。这个描述比较官方。简单总结一下,大家应该都追剧了。强引用就像男主,怎么死都死不了。软语录就像女主角,有过一段经历,却没有走到尽头。弱参照是男二号,注定要被牺牲。伪引用是路人甲。3.内存泄漏的原因内存泄漏与ThreadLocalMap中定义的Entry类有很大关系。这个动画充分展示了ThreadLocal中对象引用的关系。需要这张高清图的朋友可以在评论区留言。由于ThreadLocal对象是弱引用,如果没有外部强引用指向它,就会被GC回收,导致Entry的Key为空(null)。如果此时没有外部强引用指向它,Value会一直被访问到,如果没有,应该会被GC回收,但是因为Entry对象仍然强引用了Value,所以无法回收Value。这时候就发生了“内存泄漏”,Value变成了一个永远无法访问的对象,但是不能访问。回收对象。Entry对象属于ThreadLocalMap,ThreadLocalMap属于Thread。如果线程本身的生命周期很短,短时间内就会被销毁,那么“内存泄漏”马上就解决了。只要线程被销毁,Value也会被回收。.问题在于线程本身就是一种非常宝贵的计算机资源。它很少被创建并且经常被销毁。通常通过线程池来使用,大大延长了线程的生命周期,“内存泄漏”的影响也会越来越大。最后,让我用一句话总结一下。threadLocals对象中的Entry对象不再使用后,如果不及时清除Entry对象,程序本身又不能通过垃圾回收机制自动清除,就可能发生内存泄漏。4.如何避免内存泄漏?听到“内存泄漏”就不敢用ThreadLocal了。只要规范使用,就没有问题。给你几个小技巧:记得每次用完ThreadLocal后调用remove()方法清除数据。尽量将ThreadLocal变量定义为staticfinal,避免频繁创建ThreadLocal实例。这样既保证了程序中始终存在对ThreadLocal的强引用,又保证了随时可以通过ThreadLocal的弱引用访问到Entry的Value值,然后清空。当然,即使使用不规范,ThreadLocal也做了一些内部优化。比如调用set()方法时,ThreadLocal会进行样本清洗和全量清洗,展开时会继续检查。调用get()方法时,如果没有直接命中或者反向循环查找,也会被清理掉。调用remove()时,除了清理当前Entry外,还会向后继续清理。