ThreadLocal真的会造成内存泄漏吗?

本文探讨了ThreadLocal在并发编程中的使用,解释了其基本原理,分析了强引用和弱引用对内存泄漏的影响,并揭示了内存泄漏的真实原因在于ThreadLocalMap的生命周期与线程同步。作者给出了避免ThreadLocal内存泄漏的建议和ThreadLocal内部的优化机制。
摘要由CSDN通过智能技术生成

ThreadLoca在并发场景中,应用非常多。那ThreadLocal是不是真的会造成内存泄漏?今天给大家做一个分享,个人见解,仅供参考。

1、ThreadLocal的基本原理

简单介绍一下ThreadLocal,在多线程并发访问同一个共享变量的情况下,如果不做同步控制的话,就可能会导致数据不一致的问题,所以,我们需要使用synchronized加锁来解决。而ThreadLocal换了一个思路来处理多线程的情况,

微信截图_20240113161952.png

ThreadLocal本身并不存储数据,它使用了线程中的threadLocals属性,threadLocals的类型就是在ThreadLocal中的定义的ThreadLocalMap对象,当调用ThreadLocal的set(T value)方法时,ThreadLocal将自身的引用也就是this作为Key,然后,把用户传入的值作为Value存储到线程的ThreadLocalMap中,这就相当于每个线程的读写操作都是基于线程自身的一个私有副本,线程之间的数据是相互隔离的,互不影响。这样一来基于ThreadLocal的操作也就不存在线程安全问题了。它相当于采用了用空间来换时间的思路,从而提高程序的执行效率。

2、四种对象引用

在ThreadLocalMap内部,维护了一个Entry数组table的属性,用来存储键值对的映射关系,来看这样一段代码片段:

static class ThreadLocalMap {
 
    ...
    private Entry[] table;
    static class Entry implements WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    ...
}

Entry将ThreadLocal作为Key,值作为Value保存,它继承自WeakReference,注意构造函数里的第一行代码super(k),这意味着ThreadLocal对象是一个「弱引用」。有的小伙伴可能对「弱引用」不太熟悉,这里再介绍一下Java的四种引用关系。在JDK1.2之后,Java对引用的概念做了一些扩充,将引用分为“强”、“软”、“弱”、“虚”四种,由强到弱依次为:

image.png

强引用:指代码中普遍存在的赋值行为,如:Object o = new Object(),只要强引用关系还在,对象就永远不会被回收。

软引用:还有用处,但不是必须存活的对象,JVM会在内存溢出前对其进行回收,例如:缓存。

弱引用:非必须存活的对象,引用关系比软引用还弱,不管内存是否够用,下次GC一定回收。

虚引用:也称“幽灵引用”、“幻影引用”,最弱的引用关系,完全不影响对象的回收,等同于没有引用,虚引用的唯一的目的是对象被回收时会收到一个系统通知。

这个描述还是比较官方的,简单总结一下,大家应该都追过剧,强引用就好比是男主角,怎么都死不了。软引用就像女主角,虽有一段经历,还是没走到最后。弱引用就是男二号,注定用来牺牲的。虚引用就是路人甲了。

3、造成内存泄漏的原因

内存泄漏和ThreadLocalMap中定义的Entry类有非常大的关系。

3.1内存泄漏相关概念

Memory overflow:内存溢出,没有足够的内存提供申请者使用。Memory leak:内存泄漏是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。I内存泄漏的堆积终将导致内存溢出。

3.2 如果key使用强引用

假设ThreadLocalMap中的key使用了强引用,那么会出现内存泄漏吗?此时ThreadLocal的内存图(实线表示强引用)如下:

image.png

  1. 假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了

  2. 但是因为threadLocalMap的Entry强引用了threadLocal, 造成ThreadLocal无法被回收

  3. 在没有手动删除Entry以及CurrentThread依然运行的前提下, 始终有强引用链threadRef → currentThread → entry, Entry就不会被回收( Entry中包括了ThreadLocal实例和value), 导致Entry内存泄漏也就是说: ThreadLocalMap中的key使用了强引用, 是无法完全避免内存泄漏的

3.3 如果key使用弱引用

假设ThreadLocalMap中的key使用了弱引用, 那么会出现内存泄漏吗?

image.png

  1. 假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了

  2. 由于threadLocalMap只持有ThreadLocal的弱引用, 没有任何强引用指向threadlocal实例, 所以threadlocal就可以顺利被gc回收, 此时Entry中的key = null

  3. 在没有手动删除Entry以及CurrentThread依然运行的前提下, 也存在有强引用链threadRef → currentThread → value, value就不会被回收, 而这块value永远不会被访问到了, 导致value内存泄漏也就是说: ThreadLocalMap中的key使用了弱引用, 也有可能内存泄漏。

3.4 内存泄漏的真实原因

image.png

出现内存泄漏的真实原因出改以上两种情况比较以上两种情况,我们就会发现:内存泄漏的发生跟 ThreadLocalIMap 中的 key 是否使用弱引用是没有关系的。那么内存泄漏的的真正原因是什么呢?细心的同学会发现,在以上两种内存泄漏的情况中.都有两个前提:

  1. 没有手动侧除这个 Entry

  1. CurrentThread 依然运行

第一点很好理解,只要在使用完下 ThreadLocal ,调用其 remove 方法翻除对应的 Entry ,就能避免内存泄漏。第二点稍微复杂一点,由于ThreodLocalMap 是 Threod 的一个属性,被当前线程所引甲丁所以它的生命周期跟 Thread 一样长。那么在使用完 ThreadLocal 的使用,如果当前Thread 也随之执行结束, ThreadLocalMap 自然也会被 gc 回收,从根源上避免了内存泄漏。

综上, ThreadLocal 内存泄漏的根源是:由于ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏.

4、如何避免内存泄漏?

不要听到「内存泄漏」就不敢使用ThreadLocal,只要规范化使用是不会有问题的。我给大家支几个招:

1、每次使用完ThreadLocal都记得调用remove()方法清除数据。

2、将ThreadLocal变量尽可能地定义成static final,避免频繁创建ThreadLocal实例。

这样也就保证程序一直存在ThreadLocal的强引用,也能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的Value值,进而清除掉。

当然,就是使用不规范,ThreadLocal内部也做了一些优化,比如:

1、调用set()方法时,ThreadLocal会进行采样清理、全量清理,扩容时还会继续检查。

2、调用get()方法时,如果没有直接命中或者向后环形查找时也会进行清理。

3、调用remove()时,除了清理当前Entry,还会向后继续清理。

文章转载自:咬到舌头的小蛇

原文链接:https://www.cnblogs.com/zhaotianen/p/17962653

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

  • 14
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal是一种Java多线程并发编程技术,它提供了一种线程本地存储的机制。每个线程都有自己独立的一个ThreadLocal变量副本,线程可以通过这个变量副本来存取自己线程内的数据,而不和其他线程的数据产生冲突。 ThreadLocal的作用是为每个线程提供一个独立的变量副本,以保证线程安全。在多线程并发编程中,共享变量的修改可能被其他线程访问到,从而导致数据不一致的问题。而使用ThreadLocal可以保证每个线程之间的数据完全隔离,避免线程安全问题。 ThreadLocal的实现原理是利用了Thread对象内部的一个ThreadLocalMap实现,ThreadLocalMap中以ThreadLocal对象作为key,以变量副本作为value。每个线程都有自己的ThreadLocalMap,ThreadLocal的get和set方法就是操作当前线程的ThreadLocalMap中的变量副本。 ThreadLocal和synchronized的区别在于,synchronized是一种同步锁机制,它可以保证同一时间只有一个线程访问共享资源,从而保证线程安全。但是synchronized需要获取锁,造成线程阻塞,从而影响程序的性能。而ThreadLocal是一种线程本地存储机制,不需要锁,可以提高程序的并发性能。 虽然ThreadLocal可以解决线程安全问题,但是如果没有正确使用,也导致内存泄漏问题。因为ThreadLocalMap中的变量副本是与线程绑定的,如果线程不被正确回收,那么变量副本也不被回收,从而导致内存泄漏。为了避免这种情况,我们需要在使用完ThreadLocal后,调用remove方法,手动删除对应的变量副本,或者使用ThreadLocal的弱引用方式来避免内存泄漏

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值