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是如何实现的?

再看一下开头的图:

(这里不放源码了,写一个伪代码帮大家理解,源代码会复杂一些,有兴趣可以去翻一翻)

  1. 当我们通过new ThreadLocal()方法创建对象时,只是在堆上创建了一个对象,仅此而已。
  2. 当我们调用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. 总结

  1. ThreadLocal很好用,但不简单。
  2. ThreadLocal的内存泄露有两个对象,一个是threadLocal本身(会被自动GC掉),一个是对应的value值,每次用完之后最好手动调用remove方法手动释放。

【ThreadLocal】ThreadLocal精讲,通过伪代码带你深入了解ThreadLocal相关推荐

  1. 面试官系统精讲Java源码及大厂真题 - 44 场景实战:ThreadLocal 在上下文传值场景下的实践

    44 场景实战:ThreadLocal 在上下文传值场景下的实践 开篇语 我们在 <打动面试官:线程池流程编排中的运用实战>一文中将流程引擎简单地完善了一下,本文在其基础上继续进行改造,建 ...

  2. 面试官系统精讲Java源码及大厂真题 - 43 ThreadLocal 源码解析

    43 ThreadLocal 源码解析 引导语 ThreadLocal 提供了一种方式,让在多线程环境下,每个线程都可以拥有自己独特的数据,并且可以在整个线程执行过程中,从上而下的传递. 1 用法演示 ...

  3. 2.7mnist手写数字识别之训练调试与优化精讲(百度架构师手把手带你零基础实践深度学习原版笔记系列)

    2.7mnist手写数字识别之训练调试与优化精讲(百度架构师手把手带你零基础实践深度学习原版笔记系列) 目录 2.7mnist手写数字识别之训练调试与优化精讲(百度架构师手把手带你零基础实践深度学习原 ...

  4. java虚拟机精讲_Java虚拟机精讲 (高翔龙著) 带书签目录 中文PDF扫描版[63MB]

    <Java虚拟机精讲>内容简介:HotSpot VM是目前市面上高性能JVM的代表作之一,它采用解释器+JIT 编译器的混合执行引擎,使得Java 程序的执行性能从此有了质的飞跃.本书以极 ...

  5. RabbitMQ消息中间件技术精讲全集

    RabbitMQ消息中间件技术精讲 导航: RabbitMQ消息中间件技术精讲 一. 主流消息中间件介绍 1.1 ActiveMQ 1.2 Kafka 1.3 RocketMQ 1.4 RabbitM ...

  6. 我的新书 asp net开发技巧精讲

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 搜    ...

  7. 面试精讲(guigu)

    文章目录 精讲 1. 基础部分 2. 对象 3. 原型 4. 预解析 5.执行上下文 6. 作用域 7. 闭包 9. 同步/异步 11. Promise 精讲 1. 基础部分 变量:用来存放数据,保存 ...

  8. 水稻微生物组时间序列分析精讲1-模式图与主坐标轴分析

    写在前面 上周五我们分享了3月底发表的的 <水稻微生物组时间序列分析>的文章,大家对其中图绘制过程比较感兴趣.一上午收到了超30条留言,累计收到41个小伙伴的留言求精讲. 我们也争取花时间 ...

  9. 精讲深度学习RNN三大核心点,三分钟掌握循环神经网络

    本文将剖析循环神经网络(RNN)的工作原理,精讲循环神经网络的特点和实现方式.野蛮智能,小白也能看懂的人工智能. 循环神经网络从何而来? 我在我的这篇文章介绍了卷积神经网络(CNN)卷积神经网络(CN ...

最新文章

  1. “手机编码速度大 PK”,你玩程序猿撸月饼了么?
  2. python详细安装教程3.8-手把手教你安装Python3.8环境
  3. 【树莓派】更新系统镜像下载地址,可能是最简单粗暴的树莓派搭建个人网站教程...
  4. menisa mysql_实例详细说明linux下去除重复行命令uniq
  5. RedisTemplate常用集合使用说明-opsForList(三)
  6. html怎么设置锯齿边框样式,CSS3实现边框锯齿效果
  7. 【转载】为了我们的SZ4J代码
  8. Python实例讲解 -- 操作数据库 附mysqldb win32 py2.7下载
  9. Galaxy Note 20新爆料:至少有两款机型,处理器高低配
  10. 元胞自动机交通流模型c++_MATLAB——含出入匝道的交织区快速路元胞自动机模型...
  11. 反编译工具Reflector下载
  12. (转)步进电机扭矩计算公式
  13. Gson的使用——Gson解析json数组并展示在ListView控件上
  14. markdown编辑器——文字颜色、大小、字体、背景色、图片大小与居中对齐的设置方法
  15. 递归专题---[2]开根号
  16. nginx配置路径、跨域、本机自定义server_name
  17. 中兴通讯能制造服务器吗,中兴通讯发布边缘计算服务器 实现边缘人工智能
  18. 回归综合案例——利用回归模型预测鲍鱼年龄
  19. jq及html通过url下载文件
  20. word插入图片,嵌入型,无效果

热门文章

  1. appium报错 Stderr: ‘Security exception: Permission Denial: starting Intent { act=android.inte.....解释
  2. 如何保证用户进行积分兑换商品
  3. 如何学习自动化测试?
  4. Java利用继承和多态来求矩形、正方形和圆形的面积与周长
  5. 【校招VIP】“推电影”第一期电影详情模块Java开发文档作业评审1
  6. PAPR论文阅读笔记2之On the distribution of the peak-to-average power ratio in OFDM signals
  7. mac php phpize,MAC 下 phpize命令出错
  8. 如何制作并使用ico图标呢?
  9. 蜂蜜水润肠道,但是能缓解胃炎吗?
  10. iOS - Swift - 语法糖