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

面试:了解 ThreadLocal 内存泄漏需要满足的 2 个条件吗?

欢迎关注公众号 【11来了】(文章末尾即可扫码关注) ,持续 中间件源码、系统设计、面试进阶相关内容

在我后台回复 「资料」 可领取 编程高频电子书!
在我后台回复「面试」可领取 30w+ 字的硬核面试笔记!
示例图片
感谢你的关注!

面试:了解 ThreadLocal 内存泄漏需要满足的 2 个条件吗?

什么是 ThreadLocal?

ThreadLocal 用于存储线程本地的变量,如果创建了一个 ThreadLocal 变量,在多线程环境下访问这个变量的时候,每个线程都会在自己线程的本地内存中创建一份变量的副本,从而起到 线程隔离 的作用

Thread、ThreadLocal、ThreadLocalMap 之间的关系

1706850199755

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程所有的 ThreadLocal 对象及其对应的值

ThreadLocalMap由一个个的Entry<key,value>对象构成,Entry继承自weakReference<ThreadLocal<?>>,一个EntryThreadLocal对象和Object构成

  • Entry 的 key 是ThreadLocal对象,并且是一个弱引用。当指向key的强引用消失后,该key就会被垃圾收集器回收
  • Entry 的 value 是对应的变量值,Object 对象

当执行set方法时,ThreadLocal首先会获取当前线程 Thread 对象,然后获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象作为key,设置对应的 value。

由于每一条线程均含有各自私有的 ThreadLocalMap 对象,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也就无需使用同步机制来保证多条线程访问容器的互斥性

ThreadLocal 使用场景

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传送,打破层次间的约束。

即如果一个User对象需要从Controller层传到Service层再传到Dao层,那么把User放在ThreadLocal中,每次使用ThreadLocal来进行获取即可

2、线程间数据隔离

3、进行事务操作,用于存储线程事务信息

4、数据库连接,Session会话管理

ThreadLocal 的内存泄漏问题

先说一下什么情况下会发生内存泄漏,需要满足 2 个条件:

  • 线程内部的 ThreadLocalMap 存储的数据一直未被清理
  • 线程持续存活(线程处在线程池中),导致线程内部的 ThreadLocalMap 对象一直未被回收

接下来说一下什么时候,会符合上边的两个条件,首先对于 第一个条件 来说,有两种情况:ThreadLocal 定义为局部变量、ThreadLocal 定义为全局静态变量

ThreadLocal 被定义为局部变量

当 ThreadLocal 被定义为方法中的 局部变量 ,那么当线程进入该方法的时候,就会将 ThreadLocal 的引用给加载到线程的

如下图所示,在线程栈 Stack 中,有两个变量,ThreadLocalRef 和 CurrentThreadRef,分别指向了声明的局部变量 ThreadLocal ,以及当前执行的线程内部的 ThreadLocalMap 变量

image-20241019135606046

当线程执行完该方法之后,就会将该方法局部变量从栈中删除,因此Stack 线程栈中的 ThreadLocalRef 变量就会被弹出栈,因此 ThreadLocal 变量的强引用消失了,那么 ThreadLocal 变量只有 Entry 中的 key 对他引用,并且还是弱引用,因此这个 ThreadLocal 变量会被垃圾回收器给回收掉,导致 Entry 中的 key 为 null,而 value 还指向了对 Object 的强引用,因此 value 还一直存在 ThreadLocalMap 变量中,如下图:

image-20241019140113170

此时可以看到,ThreadLocalMap 内部 Entry 的 key(ThreadLocal)为 null,因此无法通过 key 去访问到这个 value,导致这个 value 一直无法被回收

因此 JDK 设计者在设计 ThreadLocal 时还添加了清除 ThreadLocalMap 中 key 为 null 的 value,避免内存泄漏,这是在设计时为了避免内存泄漏而采取的措施,而我们使用的时候要保持良好的编程规范,也要手动去 remove,避免内存泄露的发生

ThreadLocal 被定义为全局静态变量

如果定义 ThreadLocal 为 private static final ,那么这个 ThreadLocal 就会在常量池中存储,而不是存储在堆中,因此 ThreadLocal 并不会被回收,也就不会出现 ThreadLocalMap 的 Entry 中 key == null 的情况

这时候要考虑的问题是当前线程在使用完 ThreadLocal 之后要主动 remove,避免数据一直存储在对应的 ThreadLocalMap 中,从而出现脏数据以及内存泄漏

接下来说一下发生内存泄漏需要满足的第二个条件:线程持续存活

在梳理第一个条件(ThreadLocalMap 中的数据未被清理)时,有两种情况,一种是 ThreadLocal 被定义为局部变量,另一种是 ThreadLocal 被定义为全局静态变量

  • 当 ThreadLocal 被定义为局部变量时,会出现 ThreadLocalMap 中 key == null 的情况,在 JDK 内部会主动清理 key == null 的value,因此这种情况不会出现内存泄漏

  • 当 ThreadLocal 被定义为全局静态变量时,此时 ThreadLocalMap 内部的 key 永远不会被回收,因此如果使用之后不手动 remove 对应变量,就会导致对应的值一直存活在当前线程中,如果此时再满足第二个条件 线程持续存活 ,就会导致对应的值一直不会被回收,出现内存泄漏

当使用 线程池 执行任务时, 核心线程会一直存活 ,就会导致该线程内部的 ThreadLocalMap 变量不会清理,从而导致对应的数据一直在内存中存活, 出现内存泄漏问题 ,因此在使用 ThreadLocal 是一定要遵守正确的使用规范,避免出现内存泄漏

ThreadLocal 使用规范

ThreadLocal 正确的使用方法:

  • 将 ThreadLocal 变量定义成 private static final,这样就一直存在 ThreadLocal 的强引用,也能保证任何时候都能通过 ThreadLocal 的访问到 Entry 的 value 值,进而清除掉
  • 每次使用完 ThreadLocal 都调用它的 remove() 方法清除数据

下面给出 ThreadLocal 的用法:

public class ThreadLocalExample {private static final ThreadLocal<Integer> counter = new ThreadLocal<Integer>() {@Overrideprotected Integer initialValue() {return 0;}};public static void main(String[] args) {Thread t1 = new Thread(() -> {try {int value = counter.get(); // 获取当前线程的副本值counter.set(value + 1); // 修改副本值System.out.println("Thread " + Thread.currentThread().getName() + " value: " + counter.get());} finally {// 手动移除counter.remove(); // 在线程结束时移除变量}});Thread t2 = new Thread(() -> {try {int value = counter.get();counter.set(value + 1);System.out.println("Thread " + Thread.currentThread().getName() + " value: " + counter.get());} finally {// 手动移除counter.remove();}});t1.start();t2.start();}
}

http://www.mrgr.cn/news/53814.html

相关文章:

  • AI编程新纪元:Cursor与V0引领的技术变革
  • 离线配置安装mysql5.7主从同步数据库配置手册-亲测完美
  • 学习写作--polyGCL.md
  • Python 自编码器(Autoencoder)算法详解与应用案例
  • 结构化分析与设计(绪论)
  • 戴尔电脑win11找不到D盘的解决办法
  • 大话设计模式解读08-外观模式
  • python 函数
  • 嘉兴自闭症咨询全托机构:全面支持孩子成长的专业团队
  • 如何让审批更加的省钱?
  • 什么是DevOps,如何才能获取DevOps相关实践
  • 石墨烯磁表面等离子体
  • 对接金蝶云星空存货档案到MES系统的详细步骤及javajs动态脚本拉取的实现
  • 【C++初阶】一文讲通默认成员函数~类和对象(中)
  • Java项目-基于springboot框架的社区疫情防控平台系统项目实战(附源码+文档)
  • 【MySQL】设置二进制日志文件自动过期,从根源上解决占满磁盘的问题:通过修改 binlog_expire_logs_seconds 配置项
  • 使用C语言实现一个任务调度系统
  • 现代数字信号处理I-P4 CRLB+LMMSE 学习笔记
  • Olap数据处理
  • 智慧社区Web平台:Spring Boot技术实现
  • 高级SQL技巧:掌握数据分析与优化的艺术
  • 自由学习记录(10)
  • 【win11】终端/命令提示符/powershell美化
  • ProteinMPNN中EncLayer类介绍
  • 软件设计的依赖反转原则
  • 这种V带的无极变速能用在新能源汽车上吧?