【多线程并发】从源码层面分析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)的效果(个人理解,欢迎指正👏)

相关推荐
哎呦没18 分钟前
SpringBoot框架下的资产管理自动化
java·spring boot·后端
2401_8576009521 分钟前
SpringBoot框架的企业资产管理自动化
spring boot·后端·自动化
m0_571957582 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
Chrikk6 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*6 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue6 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man6 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang