首先,看一张整体的结构图,来帮助理解

什么是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及相关的内存泄露问题相关推荐

  1. 一文搞懂 ThreadLocal 原理

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 武培轩 来源 | 公众号「武培轩」 当多线程访 ...

  2. 一文搞懂 Python 的 import 机制

    一.前言 希望能够让读者一文搞懂 Python 的 import 机制 1.什么是 import 机制? 通常来讲,在一段 Python 代码中去执行引用另一个模块中的代码,就需要使用 Python ...

  3. python语言语句快的标记是什么_一文搞懂Python程序语句

    原标题:一文搞懂Python程序语句 程序流 Python 程序中常用的基本数据类型,包括: 内置的数值数据类型 Tuple 容器类型 String 容器类型 List 容器类型 自然的顺序是从页面或 ...

  4. 一文搞懂HMM(隐马尔可夫模型)-Viterbi algorithm

    ***一文搞懂HMM(隐马尔可夫模型)*** 简单来说,熵是表示物质系统状态的一种度量,用它老表征系统的无序程度.熵越大,系统越无序,意味着系统结构和运动的不确定和无规则:反之,,熵越小,系统越有序, ...

  5. 一文搞懂AWS EC2, IGW, RT, NAT, SG 基础篇下

    B站实操视频更新 跟着拉面学习AWS--EC2, IGW, RT, NAT, SG 简介 长文多图预警,看结论可以直接拖到"总结"部分 本文承接上一篇文章介绍以下 AWS 基础概念 ...

  6. 一文搞懂 Traefik2.1 的使用

    原文链接:一文搞懂 Traefik2.1 的使用 一文搞懂 Traefik2.1 的使用 核心概念 安装 ACME 中间件 灰度发布 流量复制 TCP 简单 TCP 服务 带 TLS 证书的 TCP ...

  7. 一文搞懂指标采集利器 Telegraf

    作者| 姜闻名 来源|尔达 Erda 公众号 ​ 导读:为了让大家更好的了解 MSP 中 APM 系统的设计实现,我们决定编写一个<详聊微服务观测>系列文章,深入 APM 系统的产品.架构 ...

  8. 《一文搞懂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发展 ...

  9. 一文搞懂什么是 PostCSS

    一文搞懂什么是 PostCSS 在 Web 应用开发中,CSS 代码的编写是重要的一部分.CSS 规范从最初的 CSS1 到现在的 CSS3,再到 CSS 规范的下一步版本,规范本身一直在不断的发展演 ...

最新文章

  1. 1xx、101、100 状态详解
  2. 工业4.0时代企业如何用CRM实现模式变革
  3. 36、JAVA_WEB开发基础之监听器
  4. SQL Server 登录更换【Windows身份验证】为【SQL Server 身份验证】
  5. 简约好看的域名售卖页html源码
  6. adapter 适配器
  7. 动态链接库的问题解决
  8. 宅家办公不宅心,送3本技术好书
  9. 新浪微博 API 使用入门
  10. 迁移到MySQL的语法转换工具初步设计
  11. 数据库 sqlserver实现插入随机姓名性别民族
  12. 五大领域总目标指南_幼儿园五大领域总目标
  13. Linux - send 出现 Resource temporarily unavailable
  14. 傻子也能看懂的弗洛伊德算法(转)
  15. 关于举办“2020·中国边缘计算企业20强”榜单评选通知
  16. Vue的生命周期是什么
  17. 基于eigen实现matlab hamming hann blakman 窗函数的实现
  18. matlab 发布商,MathWork发布MATLAB和Simulink版本2021a
  19. unity3d 关于如何画扇形
  20. 完美解决win10打不开设置,右键个性化显示设置等就出现,该文件没有与之关联的程序来执行该操作。

热门文章

  1. Musical Theme pku1743 (后缀数组)
  2. 线性代数五之高斯消元——[SDOI2010]外星千足虫,[HNOI2013]游走,[HNOI2011]XOR和路径,[hdu 4035]Maze
  3. DP专练4:[SCOI 2010]股票交易(单调队列优化dp)
  4. CF1612G Max Sum Array
  5. ARC132D-Between Two Binary Strings【贪心】
  6. P6076-[JSOI2015]染色问题【组合数学,容斥】
  7. P2657-[SCOI2009]windy数【数位dp,dfs】
  8. ssl1333-地鼠的困境【二分图,最大匹配,图论】
  9. 【图论】清理牛棚/Cleaning Shifts S(luogu 4644)
  10. 【DP】集合问题(2015特长生 T4/luogu 1466)