一文搞懂ThreadLocal及相关的内存泄露问题
首先,看一张整体的结构图,来帮助理解
什么是ThreadLocal
ThreadLocal用于创建线程局部变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题
ThreadLocal的简单使用
package test;public class ThreadLocalTest {static ThreadLocal<String> threadLocal= new ThreadLocal<>();public static void main(String[] args) {Thread t1 = new Thread(()->{//设置线程1的本地变量的值threadLocal.set("线程1");System.out.println(threadLocal.get());threadLocal.remove();System.out.println("after remove : " + threadLocal.get());});Thread t2 = new Thread(()->{//设置线程2的本地变量threadLocal.set("线程2");System.out.println(threadLocal.get());threadLocal.remove();System.out.println("after remove : " + threadLocal.get());});t1.start();t2.start();}
}
输出结果:
由此可知,虽然threadLocal为成员变量,即线程共享,但使用threadLocal在不用线程进行赋值、获取等操作时,不同线程的操作互不相扰。
那么ThreadLocal是如何作为成员变量,却能在实现多线程下数据不共享,作为线程局部变量?
ThreadLocal的原理
回到一开始的图:
ThreadLocal
并不实际存储数据,而是作为一个工具类,提供get、set方法根据当前线程用于操作当前线程中的实际数据- 每个
Thread
(线程)中都持有一个ThreadLocal.ThreadLocalMap
类型的成员变量,初始值为null。ThreadLocal的get、set方法实际上就是在操作这个成员变量。 ThreadLocalMap
持有一个Entry[]
类型的成员变量table,类似JDK1.7的HashMap中的Entry[] table
可以类比学习Entry
key为ThreadLocal<?>
类型的的弱引用,value为Object
类型的强引用
ThreadLocal的set()
public void set(T value) {//1、获取当前线程(调用者线程)Thread t = Thread.currentThread();//2、获取当前线程的ThreadLocalMap变量ThreadLocalMap map = getMap(t);//3、如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值if (map != null)map.set(this, value);//4、如果map为null,说明首次添加,需要首先创建出对应的mapelsecreateMap(t, value);
}ThreadLocalMap getMap(Thread t) {//获取线程的threadLocalsreturn t.threadLocals;
}void createMap(Thread t, T firstValue) {//创建线程的threadLocals, this为ThreadLocal<?>的引用t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal的get()
在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null。
引用自:https://www.cnblogs.com/fsmly/p/11020641.html#_label0
public T get() {//1、获取当前线程Thread t = Thread.currentThread();//2、获取当前线程的threadLocals变量ThreadLocalMap map = getMap(t);//3、如果threadLocals变量不为null,就可以在map中查找到本地变量的值if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//4、执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量return setInitialValue();
}private T setInitialValue() {//protected T initialValue() {return null;}T value = initialValue();//获取当前线程Thread t = Thread.currentThread();//以当前线程作为key值,去查找对应的线程变量,找到对应的mapThreadLocalMap map = getMap(t);//如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值if (map != null)map.set(this, value);//如果map为null,说明首次添加,需要首先创建出对应的mapelsecreateMap(t, value);return value;
}
ThreadLocal操作总结
根据上述源码的分析可知:ThreadLocal最终操作的都是调用线程的ThreadLocalMap成员变量,因此不同线程使用同一个ThreadLocal成员变量互不相扰。
每个线程内部有一个ThreadLocal.ThreadLocalMap
类型threadLocals
的成员变量,该变量的类型为类似于HashMap,其中的key为当前定义的ThreadLocal变量的this引用,value为使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。
ThreadLocal使用不当造成内存泄漏问题
弱引用
弱引用(这里讨论ThreadLocalMap中的Entry类的重点):如果一个对象只具有弱引用,那么这个对象就会被垃圾回收器GC掉(被弱引用所引用的对象只能生存到下一次GC之前,当发生GC时候,无论当前内存是否足够,弱引用所引用的对象都会被回收掉)。弱引用也是和一个引用队列联合使用,如果弱引用的对象被垃圾回收期回收掉,JVM会将这个引用加入到与之关联的引用队列中。若引用的对象可以通过弱引用的get方法得到,当引用的对象呗回收掉之后,再调用get方法就会返回null
ThreadLocalMap内部实际上是一个Entry数组:
Entry
是继承自WeakReference
(弱引用)的一个类。Entry
的key是指向ThreadLocal
的弱引用,value一般为强引用
当一个线程调用ThreadLocal的set方法设置变量的时候,当前线程的ThreadLocalMap就会存放一个记录(Entry
),这个记录的key值为ThreadLocal的弱引用,value就是通过set设置的值。
如果当前线程一直存在且没有调用该ThreadLocal的remove方法,此时存在两种情况:
- ThreadLocalMap之外存在ThreadLocal的引用:那么当前线程中的ThreadLocalMap中会存在对ThreadLocal变量的引用和value对象的引用,无法进行垃圾回收,导致这些本地变量一直存在,可能会出现内存溢出
- ThreadLocalMap之外不存在ThreadLocal的引用:因为ThreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,所以下一次垃圾回收时ThreadLocal(key)将被回收。此时ThreadLocalMap中就可能存在key为null但是value不为null的现象,出现内存泄漏。
因此每次使用完ThreadLocal,建议调用它的remove()方法,清除数据,避免内存泄漏
内存泄漏问题总结
ThreadLocalMap
中的Entry
的key使用的是ThreadLocal
对象的弱引用,在没有其他地方对ThreadLocal
存在依赖时,ThreadLocalMap
中的ThreadLocal
对象(即key)就会被回收,而如果其他地方存在ThreadLocal
的引用则不会被回收。当key被回收时,Map中就可能存在key为null但是value不为null的现象,出现内存泄漏。
因此每次使用完ThreadLocal,建议调用它的remove()方法,清除数据,避免内存泄漏。
参考
- ThreadLocal
- Java中的ThreadLocal详解
- TheadLocal 引起的内存泄露故障分析
一文搞懂ThreadLocal及相关的内存泄露问题相关推荐
- 一文搞懂 ThreadLocal 原理
点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 武培轩 来源 | 公众号「武培轩」 当多线程访 ...
- 一文搞懂 Python 的 import 机制
一.前言 希望能够让读者一文搞懂 Python 的 import 机制 1.什么是 import 机制? 通常来讲,在一段 Python 代码中去执行引用另一个模块中的代码,就需要使用 Python ...
- python语言语句快的标记是什么_一文搞懂Python程序语句
原标题:一文搞懂Python程序语句 程序流 Python 程序中常用的基本数据类型,包括: 内置的数值数据类型 Tuple 容器类型 String 容器类型 List 容器类型 自然的顺序是从页面或 ...
- 一文搞懂HMM(隐马尔可夫模型)-Viterbi algorithm
***一文搞懂HMM(隐马尔可夫模型)*** 简单来说,熵是表示物质系统状态的一种度量,用它老表征系统的无序程度.熵越大,系统越无序,意味着系统结构和运动的不确定和无规则:反之,,熵越小,系统越有序, ...
- 一文搞懂AWS EC2, IGW, RT, NAT, SG 基础篇下
B站实操视频更新 跟着拉面学习AWS--EC2, IGW, RT, NAT, SG 简介 长文多图预警,看结论可以直接拖到"总结"部分 本文承接上一篇文章介绍以下 AWS 基础概念 ...
- 一文搞懂 Traefik2.1 的使用
原文链接:一文搞懂 Traefik2.1 的使用 一文搞懂 Traefik2.1 的使用 核心概念 安装 ACME 中间件 灰度发布 流量复制 TCP 简单 TCP 服务 带 TLS 证书的 TCP ...
- 一文搞懂指标采集利器 Telegraf
作者| 姜闻名 来源|尔达 Erda 公众号 导读:为了让大家更好的了解 MSP 中 APM 系统的设计实现,我们决定编写一个<详聊微服务观测>系列文章,深入 APM 系统的产品.架构 ...
- 《一文搞懂NMS发展历程》Soft-NMS、Weighted NMS、IoU-Net、Softer-NMS、Adaptive NMS、DIoU-NMS
<一文搞懂NMS发展历程>Soft-NMS.Weighted NMS.IoU-Net.Softer-NMS.Adaptive NMS.DIoU-NMS 文章目录 <一文搞懂NMS发展 ...
- 一文搞懂什么是 PostCSS
一文搞懂什么是 PostCSS 在 Web 应用开发中,CSS 代码的编写是重要的一部分.CSS 规范从最初的 CSS1 到现在的 CSS3,再到 CSS 规范的下一步版本,规范本身一直在不断的发展演 ...
最新文章
- 1xx、101、100 状态详解
- 工业4.0时代企业如何用CRM实现模式变革
- 36、JAVA_WEB开发基础之监听器
- SQL Server 登录更换【Windows身份验证】为【SQL Server 身份验证】
- 简约好看的域名售卖页html源码
- adapter 适配器
- 动态链接库的问题解决
- 宅家办公不宅心,送3本技术好书
- 新浪微博 API 使用入门
- 迁移到MySQL的语法转换工具初步设计
- 数据库 sqlserver实现插入随机姓名性别民族
- 五大领域总目标指南_幼儿园五大领域总目标
- Linux - send 出现 Resource temporarily unavailable
- 傻子也能看懂的弗洛伊德算法(转)
- 关于举办“2020·中国边缘计算企业20强”榜单评选通知
- Vue的生命周期是什么
- 基于eigen实现matlab hamming hann blakman 窗函数的实现
- matlab 发布商,MathWork发布MATLAB和Simulink版本2021a
- unity3d 关于如何画扇形
- 完美解决win10打不开设置,右键个性化显示设置等就出现,该文件没有与之关联的程序来执行该操作。
热门文章
- Musical Theme pku1743 (后缀数组)
- 线性代数五之高斯消元——[SDOI2010]外星千足虫,[HNOI2013]游走,[HNOI2011]XOR和路径,[hdu 4035]Maze
- DP专练4:[SCOI 2010]股票交易(单调队列优化dp)
- CF1612G Max Sum Array
- ARC132D-Between Two Binary Strings【贪心】
- P6076-[JSOI2015]染色问题【组合数学,容斥】
- P2657-[SCOI2009]windy数【数位dp,dfs】
- ssl1333-地鼠的困境【二分图,最大匹配,图论】
- 【图论】清理牛棚/Cleaning Shifts S(luogu 4644)
- 【DP】集合问题(2015特长生 T4/luogu 1466)