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

ThreadLocal 内存泄漏问题

1. 认识ThreadLocal

java中提高了threadlocal,为每个线程保存其独有的变量,threadlocal使用的一个小例子是:

public class ThreadLocalTest {public static void main(String[] args) {ThreadLocal<String> threadIds = new ThreadLocal<>();ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {fixedThreadPool.execute(() -> {threadIds.set("" + Thread.currentThread().getId());System.out.println(Thread.currentThread().getName() + ", id:" + threadIds.get());});}}
}

输出结果:

Thread-0, id:12
Thread-3, id:15
Thread-2, id:14
Thread-5, id:17
Thread-1, id:13
Thread-6, id:18
Thread-4, id:16
Thread-7, id:19
Thread-9, id:21
Thread-8, id:20

可以看出,每个线程内部都保存了一个不同的threadId。

2. ThreadLocal实现原理

每个线程(Thread类的实例)都具有一个叫做threadLocalMap的变量,其是一个map类型的结构,其中每个key指向一个threadLocal实例,value存储当前线程在这个threadLocal中存储的值。举例来说,在上面的程序中我们创建了10个线程,每个线程内部的threadLocalMap都具有一个entry<threadIds, v>。因此,每个线程记录、更改threadLocal中的值实际上就是在自己的threadLocalMap中找到对应的entry,然后更新entry里面的值,从而实现线程隔离。

3. ThreadLocalMap中的key是软引用

我们知道,ThreadLocalMap中的key是对ThreadLocal的引用,那为什么搞成软引用呢,我的理解是这样的。
在这里插入图片描述
图片来自于zhihu java喵
看上图,实线代表强引用,虚线代表弱引用。
我们结合上面的代码来理解一下:
首先,我们需要知道线程的寿命是很长的,我们在线程池中创建的线程之后可能还会去运行其他的runnable对象,因此需要动态地清理其threadLocalMap中无用的entry。

  1. 当main方法被执行时,main方法被压入虚拟机栈,创建了10个线程,假设线程还没终止,那么此时threadIds对象具有1个强引用(来自于main线程的ThreadLocal threadIds = new ThreadLocal<>();这句)和10个弱引用(来自于10个线程的threadLocalMap中的key)。
  2. 当main方法运行之后,main方法被弹出栈,此时threadIds对象只具有10个弱引用(来自于10个线程的threadLocalMap中的key)。
  3. java gc线程发现threadIds对象只有弱引用,那么在gc时就会回收这个对象,之后每个线程的threadLocalMap中这个entry的key就变成了null。(此时entry是<null, v>)。
  4. 由于此时这条entry已经无用的,但是v还指向其引用的对象,那么v就无法被回收,造成了内存泄漏。
  5. 我刚看到这个问题时,想着解决办法也很简单,线程发现key是null之后,将v也置为null,等着gc回收v不久好了吗,实际上也确实可以这么做。

Q:为什么ThreadLocalMap的key是弱引用?
A:结合上面的流程就很简单了,如果key是弱引用,那么main方法都运行完了,threadIds都已经无用了,此时还有十个强引用指向它,这让gc怎么回收呢?

4. 内存泄漏解决方法

使用完threadLocal对象之后,调用remove方法,将其从线程的threadLocalMap中清理出去:

public class ThreadLocalTest {public static void main(String[] args) {ThreadLocal<String> threadIds = new ThreadLocal<>();ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {fixedThreadPool.execute(() -> {threadIds.set("" + Thread.currentThread().getId());System.out.println(Thread.currentThread().getName() + ", id:" + threadIds.get());// removethreadIds.remove();});}}
}
http://www.lryc.cn/news/11718.html

相关文章:

  • 【算法】两道算法题根据提供字母解决解码方法和城市的天际线天际线问题
  • Python-TCP网络编程基础以及客户端程序开发
  • 超低成本DDoS攻击来袭,看WAF如何绝地防护
  • CF1795E Explosions? (单调栈)
  • C++——二叉树排序树
  • 深拷贝浅拷贝的区别?如何实现一个深拷贝?
  • Linux应用编程下连接本地数据库进行增删改查系列操作
  • 图论学习03
  • 解决qt中cmake单独存放 .ui, .cpp, .h文件
  • 操作系统(day12)-- 基本分段存储,段页式存储
  • 疯狂弹出请插入多卷集的最后一张磁盘窗口
  • Spark12: SparkSQL入门
  • show profile和trance分析SQL
  • [AI生成图片] 效果最好的Midjourney 的介绍和使用
  • Vue.use( ) 的核心原理
  • idea同时编辑多行-winmac都支持
  • 亿级高并发电商项目-- 实战篇 --万达商城项目 十一(编写商品搜索功能、操作商品同步到ES、安装RabbitMQ与Erlang,配置监听队列与消息队列)
  • 数据结构概述和稀疏数组
  • 宝塔搭建实战人才求职管理系统adminm前端vue源码(三)
  • 服务器是干什么用的?
  • C++ 之结构体与共用体
  • Java基础知识汇总(良心总结)
  • InnoDB之Undo log格式
  • 一问学习StreamAPI终端操作
  • 在屎山代码中快速找到想要的代码法-锁表法(C#)
  • 网页设计html期末大作业
  • 实战打靶集锦-006-Stapler
  • 致远OAA6版安装
  • python实用脚本(六)—— pandas库的使用(生成、读取表格)
  • 字符集、ASCII、GBK、UTF-8、Unicode、乱码、字符编码、解码问题等