【多线程并发】从源码层面分析ThreadLocal的实现原理和内存泄露

ThreadLocal在一定程度上保证了线程安全🔐(原子性),他不让多线程去操作临界资源,让每个线程去操作属于自己的数据

ThreadLocal的实现原理

  • 每个ThreadLocal都存储着一个成员变量ThreadLocalMap

  • ThreadLocal本身不存储数据,类似一个工具类,我们基于ThreadLocal操作ThreadLocalMap

  • ThreadLocalMap本身是基于Entry[]实现的,因为一个线程可以绑定多个ThreadLocal,这样一来就可能需要存储多个数据,所以采用数组实现

  • 每一个ThreadLocalMap都是基于当前的"this"(即ThreadLocal)作为key来获取value的

  • ThreadLocalMap的key是一个弱引用(一旦GC就会回收♻️)

    • 这里是为了在ThreadLocal对象失去引用后,如果key是强引用,会导致ThreadLocal对象无法被回收

这里我们补充介绍下Java的引用类型

Java的四种引用类型

引用的强软弱虚(引用的强弱关系依次递减)

  • 强引用(=) :关系存在,即不会被垃圾回收机制回收(内存溢出抛异常也不会回收)
  • 软引用(SoftReference) :内存不够会被回收(回收后仍然OOM才会抛异常)
  • 弱引用(WeakReference) :无论内存够不够,垃圾收集器一工作就会被回收,可以解决内存泄漏的相关问题
  • 虚引用(PhantomReference) :约等于没有引用关系(甚至无法通过虚引用获取到对象实例),一般不单独使用,需要和引用队列联合使用。主要作用是跟踪对象被垃圾回收的状态,只是会在被回收后发送一条系统通知

ThreadLocal的内存泄漏问题

首先,什么是内存泄漏? 内存泄漏(Java中)指的是在程序运行过程中,被分配到堆中的某些对象,不会再被使用,但却无法被垃圾回收器释放的现象。内存泄漏会导致内存使用量不断增加,可能会导致程序越来越慢甚至崩溃。

  • 如果ThreadLocal的引用丢失,key会因为本身是弱引用而被GC回收,如果此时线程还没有被回收,就会导致内存泄漏,内存中的value无法被回收,同时也无法被获取到

    • 为了解决ThreadLocalMap中key的内存泄漏问题,采用了弱引用的方式(下图虚线)

      • 弱引用(WeakReference)是无论内存够不够,垃圾收集器一工作就会被回收,因此可以解决key的内存泄漏
    • 但是并没有解决上面所说的value的内存泄漏问题

    • 我们根据下图来简单分析下原因:

      • 图中有ABCDF五个引用,其中ABC为强引用,DF为弱引用;
      • 当AB还存在时,ThreadLocalMap、ThreadLocal_1和ThreadLocal_2都能通过引用被获取到,自然不存在内存泄漏的问题;
      • 当AB不存在时,则无法通过AB的强引用找到ThreadLocal_1和ThreadLocal_2;
      • 由于线程对象C还存在,因此ThreadLocalMap不会被当作垃圾对象被回收;
      • 在经过GC后,DF的弱引用会被回收,没有了强引用的ThreadLocal_1和ThreadLocal_2对象也会被回收;
      • 此时,则不会再有程序能通过key_2和key_4访问到他们的value(因为ThreadLocalMap是通过ThreadLocal的弱引用作为key的,此时这两个ThreadLocal已经因为没有强引用而被GC回收了),但是由于线程对象还存在,故ThreadLocalMap无法被回收,于是这两个value还将继续存在,这也就造成了内存泄漏
解决方法

只需要在使用完ThreadLocal后,已是调用remove()方法,移除Entry即可

到这里不知道有没有人跟我冒出类似的想法:既然我都记得使用remove()方法了,直接在这个方法中把key和value移除不就行了,为什么要搞个弱引用呢?个人理解是为了以防万一,由于key指向的是一个ThreadLocal对象,一般会比value大,因此用弱引用来保证较大的ThreadLocal对象会自动被回收,以达到即使忘记使用remove()方法,仍然能释放较大的对象(即ThreadLocal)的效果(个人理解,欢迎指正👏)

相关推荐
yuanbenshidiaos1 小时前
c++---------数据类型
java·jvm·c++
向宇it1 小时前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
Lojarro1 小时前
【Spring】Spring框架之-AOP
java·mysql·spring
莫名其妙小饼干1 小时前
网上球鞋竞拍系统|Java|SSM|VUE| 前后端分离
java·开发语言·maven·mssql
isolusion2 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp2 小时前
Spring-AOP
java·后端·spring·spring-aop
Oneforlove_twoforjob2 小时前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
TodoCoder2 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
向宇it3 小时前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
小蜗牛慢慢爬行3 小时前
Hibernate、JPA、Spring DATA JPA、Hibernate 代理和架构
java·架构·hibernate