【ThreadLocal】ThreadLocal精讲,通过伪代码带你深入了解ThreadLocal
ThreadLocal精讲
- 前言(可跳过)
- 1. 如何使用ThreadLocal
- 2. ThreadLocal是如何实现的?
- 3. 为什么会内存泄露?
- 4. 如何避免内存泄露?
- 5. 总结
前言(可跳过)
Java ThreadLocal是个老生常谈的话题,ThreadLocal好用的同时也伴随着内存泄露的问题,内存安全又是系统稳定性保障中绕不开的一个话题,所以深入的理解ThreadLocal显得非常重要。
ThreadLocal内存模型(大家可以先看一下有个印象,后面会细讲):
1. 如何使用ThreadLocal
ThreadLocal使用起来非常简单,看一个简单的例子:
public class ThreadLocalDemo {private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) throws InterruptedException {threadLocal.set("我爱中国!");Thread childThread = new Thread(() -> {threadLocal.set("i love china!");String data = threadLocal.get();System.out.println("inner: " + data);});childThread.start();//等待1s, 让之前的线程先打印Thread.sleep(1000);String data = threadLocal.get();System.out.println("out: " + data);}
}
打印:
inner: i love china!
out: 我爱中国!
从这个例子可以看出,threadLocal为不同的线程分别存储了对象(main线程存储的是:“我爱中国!”, childThread存储的是:“i love china!”,互不影响)。即threadLocal存储的对象是线程隔离的,这样做的好处是避免了共享内存造成的线程安全问题。这样说比较抽象,举个web开发中常见的例子:当用户请求进入拦截器后,根据用户id读取出用户的信息,然后塞到ThreadLocal中,然后在后续的任何一个地方都可以直接通过threadLocal.get()方法获取到用户信息,而不需要显示的在方法的参数中传递。还有类似的logback中的MDC对象,都是使用的ThreadLocal方式实现的,有兴趣的同学可以作为学习资料研究下。
2. ThreadLocal是如何实现的?
再看一下开头的图:
(这里不放源码了,写一个伪代码帮大家理解,源代码会复杂一些,有兴趣可以去翻一翻)
- 当我们通过
new ThreadLocal()
方法创建对象时,只是在堆上创建了一个对象,仅此而已。 - 当我们调用ThreadLocal对象的set方法时,会检查当前线程对应的Thread对象中的
threadLocalMap
属性中是否包含当前的threadLocal
对象(这里有点绕,结合图看下几个对象的关系,下面解释下),如果没有则新建,如果有则直接用。
这里以threadLocal.set("我爱中国");
为例说明存储过程(以下是伪代码):
public void set(T value) {Thread t = Thread.currentThread();//后去thread的成员变量threadLocalMap对象ThreadLocalMap threadLocalMap = t.threadLocalMap;//从threadLocalMap中找到用于存储内容的Entry[]数组Entry[] entryArr = threadLocalMap.table;for(Entry entry : entryArr){if (entry.refrent == null) {//从entryArr中删除此entry,(删除逻辑就不写了),这里跟内存泄露有关,可以先忽略不看,记得这里有这个逻辑即可,后面会说}//这里的this即当前的threadLocal对象if (entry.refrent == this) {entry.setValue(value);return;}}//不存在则新增Entry entry = new Entry();entry.referent = this;entry.value = value;entryArr.add(entry);
}
Thread对象的成员变量threadLocalMap
是通过Entry[]数组中的一个Entry对象存储threadLocal对象及其对应的value的。Entry对象包含两个成员变量:
- 一个referent(指向ThreadLocal对象,这里的referent是一个弱引用,先记得,后面讲内存泄露会说到)
- 一个value(指向要存储的内容)
所谓检查存不存在,就是检查Entry[]数组中是否包含了这个threadLocal
对象,如果存在,则取出对应的Entry对象,然后把新的value值set进去。如果不存在,则创建一个新的Entry对象保存threadLocal对象和其对应的value值
。(所以这里可以看出,threadLocal对象就是map的key)
上面说明了是set过程,get过程类似,找到Entry对象,将其中的value取出来返回即可,这里不再赘述。
3. 为什么会内存泄露?
还是通过一段代码带大家看一下:
public class DoSth implement Runnable {public void run() {optThreadLocal();//这里表示运行很多东西,执行了很长时间Thread.sleep(100000000000);}public void optThreadLocal() {ThreadLocal threadLocal = new ThreadLocal();threadLocal.set("一些内容!");}
}
Thread childThread = new Thread(new DoSth());
childThread.start();
在线程开始运行后,首先执行了optThreadLocal()方法,在方法内创建了局部变量threadLocal并调用了set方法。从上文中我们已经了解到:此时threadLocal被存储到了线程的threadLocalMap对象中。而这里optThreadLocal()方法执行完成后,作为局部变量的threadLocal引用将被回收(变量超过了作用域自然会被回收喽,即对应的方法帧从线程方法栈中被抛出了,后面会单独写一下这块内容),这时只剩下Entry的referent引用指向堆上的threadLocal对象。
如果这个线程一直执行(执行很长时间),那么threadLocal对象,以及对应的value将一直得不到回收,这时便造成了内存泄露。这里大家注意,泄露的是两个对象:一个是threadLocal对象,一个value对象。
那么难道Java的设计者们会对这种情况熟视无睹吗?显然不会,下面看一下他们是怎么做的。
4. 如何避免内存泄露?
我们再看一下开头这个图:
当图上方的方法局部变量threadLocal的强引用被回收后,就只剩下Entry对象的referent弱引用指向堆上的threadLocal对象。而我们知道,弱引用指向的对象,在下一次GC的时候会被回收,所以这时threadLocal对象会在下一次GC时被回收。这时解决了ThreadLocal对象内存泄露的问题
那value对象怎么回收呢?再看一下之前的set伪代码(为了方便大家看,直接复制过来):
public void set(T value) {Thread t = Thread.currentThread();//后去thread的成员变量threadLocalMap对象ThreadLocalMap threadLocalMap = t.threadLocalMap;//从threadLocalMap中找到用于存储内容的Entry[]数组Entry[] entryArr = threadLocalMap.table;for(Entry entry : entryArr){if (entry.referent == null) {//!!!! 重点,清理内存,从entryArr中删除此entry}//这里的this即当前的threadLocal对象if (entry.refrent == this) {entry.setValue(value);return;}}//不存在则新增Entry entry = new Entry();entry.referent = this;entry.value = value;entryArr.add(entry);
}
因为threadLocal已经被回收了,所以对应的Entry的referent将变为null,所以,当再次调用set方法时,对应的value即会被清理(在get方法中也有类似的逻辑)。
注意:这里只要在这个线程执行过程中,调用其他threadLocal对象的get/set方法也会执行清理操作
最后我们发现,如果我们后面再不调用任何threadLocal的任何方法,只要当前线程在运行,内存就得不到释放,那么如果这个value对象很占用内存,我们想要主动释放怎么办呢?这时我们可以手动调用threadLocal.remove()
方法来主动释放内存。
5. 总结
- ThreadLocal很好用,但不简单。
- ThreadLocal的内存泄露有两个对象,一个是threadLocal本身(会被自动GC掉),一个是对应的value值,每次用完之后最好手动调用remove方法手动释放。
【ThreadLocal】ThreadLocal精讲,通过伪代码带你深入了解ThreadLocal相关推荐
- 面试官系统精讲Java源码及大厂真题 - 44 场景实战:ThreadLocal 在上下文传值场景下的实践
44 场景实战:ThreadLocal 在上下文传值场景下的实践 开篇语 我们在 <打动面试官:线程池流程编排中的运用实战>一文中将流程引擎简单地完善了一下,本文在其基础上继续进行改造,建 ...
- 面试官系统精讲Java源码及大厂真题 - 43 ThreadLocal 源码解析
43 ThreadLocal 源码解析 引导语 ThreadLocal 提供了一种方式,让在多线程环境下,每个线程都可以拥有自己独特的数据,并且可以在整个线程执行过程中,从上而下的传递. 1 用法演示 ...
- 2.7mnist手写数字识别之训练调试与优化精讲(百度架构师手把手带你零基础实践深度学习原版笔记系列)
2.7mnist手写数字识别之训练调试与优化精讲(百度架构师手把手带你零基础实践深度学习原版笔记系列) 目录 2.7mnist手写数字识别之训练调试与优化精讲(百度架构师手把手带你零基础实践深度学习原 ...
- java虚拟机精讲_Java虚拟机精讲 (高翔龙著) 带书签目录 中文PDF扫描版[63MB]
<Java虚拟机精讲>内容简介:HotSpot VM是目前市面上高性能JVM的代表作之一,它采用解释器+JIT 编译器的混合执行引擎,使得Java 程序的执行性能从此有了质的飞跃.本书以极 ...
- RabbitMQ消息中间件技术精讲全集
RabbitMQ消息中间件技术精讲 导航: RabbitMQ消息中间件技术精讲 一. 主流消息中间件介绍 1.1 ActiveMQ 1.2 Kafka 1.3 RocketMQ 1.4 RabbitM ...
- 我的新书 asp net开发技巧精讲
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 搜 ...
- 面试精讲(guigu)
文章目录 精讲 1. 基础部分 2. 对象 3. 原型 4. 预解析 5.执行上下文 6. 作用域 7. 闭包 9. 同步/异步 11. Promise 精讲 1. 基础部分 变量:用来存放数据,保存 ...
- 水稻微生物组时间序列分析精讲1-模式图与主坐标轴分析
写在前面 上周五我们分享了3月底发表的的 <水稻微生物组时间序列分析>的文章,大家对其中图绘制过程比较感兴趣.一上午收到了超30条留言,累计收到41个小伙伴的留言求精讲. 我们也争取花时间 ...
- 精讲深度学习RNN三大核心点,三分钟掌握循环神经网络
本文将剖析循环神经网络(RNN)的工作原理,精讲循环神经网络的特点和实现方式.野蛮智能,小白也能看懂的人工智能. 循环神经网络从何而来? 我在我的这篇文章介绍了卷积神经网络(CNN)卷积神经网络(CN ...
最新文章
- “手机编码速度大 PK”,你玩程序猿撸月饼了么?
- python详细安装教程3.8-手把手教你安装Python3.8环境
- 【树莓派】更新系统镜像下载地址,可能是最简单粗暴的树莓派搭建个人网站教程...
- menisa mysql_实例详细说明linux下去除重复行命令uniq
- RedisTemplate常用集合使用说明-opsForList(三)
- html怎么设置锯齿边框样式,CSS3实现边框锯齿效果
- 【转载】为了我们的SZ4J代码
- Python实例讲解 -- 操作数据库 附mysqldb win32 py2.7下载
- Galaxy Note 20新爆料:至少有两款机型,处理器高低配
- 元胞自动机交通流模型c++_MATLAB——含出入匝道的交织区快速路元胞自动机模型...
- 反编译工具Reflector下载
- (转)步进电机扭矩计算公式
- Gson的使用——Gson解析json数组并展示在ListView控件上
- markdown编辑器——文字颜色、大小、字体、背景色、图片大小与居中对齐的设置方法
- 递归专题---[2]开根号
- nginx配置路径、跨域、本机自定义server_name
- 中兴通讯能制造服务器吗,中兴通讯发布边缘计算服务器 实现边缘人工智能
- 回归综合案例——利用回归模型预测鲍鱼年龄
- jq及html通过url下载文件
- word插入图片,嵌入型,无效果
热门文章
- appium报错 Stderr: ‘Security exception: Permission Denial: starting Intent { act=android.inte.....解释
- 如何保证用户进行积分兑换商品
- 如何学习自动化测试?
- Java利用继承和多态来求矩形、正方形和圆形的面积与周长
- 【校招VIP】“推电影”第一期电影详情模块Java开发文档作业评审1
- PAPR论文阅读笔记2之On the distribution of the peak-to-average power ratio in OFDM signals
- mac php phpize,MAC 下 phpize命令出错
- 如何制作并使用ico图标呢?
- 蜂蜜水润肠道,但是能缓解胃炎吗?
- iOS - Swift - 语法糖