1. 什么是ThreadLocal

ThreadLocal提供了线程的局部变量,每个线程都可以通过set()和get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离~。

简要言之:往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。

ThreadLocal用在什么地方?

讨论ThreadLocal用在什么地方前,我们先明确下,如果仅仅就一个线程,那么都不用谈ThreadLocal的,ThreadLocal是用在多线程的场景的!!!

ThreadLocal归纳下来就2类用途:

  • 保存线程上下文信息,在任意需要的地方可以获取!!!

  • 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!

保存线程上下文信息,在任意需要的地方可以获取!!!

由于ThreadLocal的特性,同一线程在某地方进行设置,在随后的任意地方都可以获取到。从而可以用来保存线程上下文信息。

常用的比如每个请求怎么把一串后续关联起来,就可以用ThreadLocal进行set,在后续的任意需要记录日志的方法里面进行get获取到请求id,从而把整个请求串起来。

还有比如Spring的事务管理,用ThreadLocal存储Connection,ThreadLocal能够实现当前线程的操作都是用同一个Connection,保证了事务!从而各个DAO可以获取同一Connection,可以进行事务回滚,提交等操作。

同样的,Hibernate对Connection的管理也是采用了相同的手法(使用ThreadLocal,当然了Hibernate的实现是更强大的)~

线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!

每个线程往ThreadLocal中读写数据是线程隔离,互相之间不会影响的,所以ThreadLocal无法解决共享对象的更新问题!

由于不需要共享信息,自然就不存在竞争问题了,从而保证了某些情况下线程的安全,以及避免了某些情况需要考虑线程安全必须同步带来的性能损失!!!

这类场景阿里规范里面也提到了:

ThreadLocal一些细节!

ThreaLocal使用示例代码:

public class ThreadLocalTest {

private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

public static void main(String[] args) {

new Thread(() -> {

try {

for (int i = 0; i < 100; i++) {

threadLocal.set(i);

System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());

try {

Thread.sleep(200);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

} finally {

threadLocal.remove();

}

}, "threadLocal1").start();

new Thread(() -> {

try {

for (int i = 0; i < 100; i++) {

System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());

try {

Thread.sleep(200);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

} finally {

threadLocal.remove();

}

}, "threadLocal2").start();

}

}

代码运行结果:

从运行的结果我们可以看到threadLocal1进行set值对threadLocal2并没有任何影响!

Thread、ThreadLocalMap、ThreadLocal总览图:

Thread类有属性变量threadLocals (类型是ThreadLocal.ThreadLocalMap),也就是说每个线程有一个自己的ThreadLocalMap ,所以每个线程往这个ThreadLocal中读写隔离的,并且是互相不会影响的。

一个ThreadLocal只能存储一个Object对象,如果需要存储多个Object对象那么就需要多个ThreadLocal!!!

如图:

看到上面的几个图,大概思路应该都清晰了,我们Entry的key指向ThreadLocal用虚线表示弱引用 。

2. ThreadLocal实现的原理

首先,我们来看一下ThreadLocal的set()方法,因为我们一般使用都是new完对象,就往里边set对象了

public void set(T value)

{

// 得到当前线程对象

Thread t = Thread.currentThread();

// 这里获取ThreadLocalMap

ThreadLocalMap map = getMap(t);

// 如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去

if (map != null)

map.set(this, value);

else

createMap(t, value);

}

上面有个ThreadLocalMap,我们去看看这是什么?

static class ThreadLocalMap

{

/**

* The entries in this hash map extend WeakReference, using

* its main ref field as the key (which is always a

* ThreadLocal object). Note that null keys (i.e. entry.get()

* == null) mean that the key is no longer referenced, so the

* entry can be expunged from table. Such entries are referred to

* as "stale entries" in the code that follows.

*/

static class Entry extends WeakReference<ThreadLocal<?>> {

/** The value associated with this ThreadLocal. */

Object value;

Entry(ThreadLocal<?> k, Object v) {

super(k);

value = v;

}

}

//....很长

}

通过上面我们可以发现的是ThreadLocalMap是ThreadLocal的一个内部类。用Entry类来进行存储

我们的值都是存储到这个Map上的,key是当前ThreadLocal对象

如果该Map不存在,则初始化一个。如果该Map存在,则从Thread中获取

Thread维护了ThreadLocalMap变量 。

可以看出,ThreadLocalMap是在ThreadLocal中使用内部类来编写的,但对象的引用是在Thread中

于是我们可以总结出:Thread为每个线程维护了ThreadLocalMap这么一个Map,而ThreadLocalMap的key是LocalThread对象本身,value则是要存储的对象

有了上面的基础,我们看get()方法就一点都不难理解了:

public T get()

{

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings("unchecked")

T result = (T)e.value;

return result;

}

}

return setInitialValue();

}

ThreadLocal原理总结

(1)每个Thread维护着一个ThreadLocalMap的引用

(2)ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储

(3)调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象

(4)调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象

(5)ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。

正因为这个原理,所以ThreadLocal能够实现“数据隔离”,获取当前线程的局部变量值,不受其他线程影响~

3. 避免内存泄露

我们来看一下ThreadLocal的对象关系引用图:

我们Entry的key指向ThreadLocal用虚线表示弱引用。

java对象的引用包括 :强引用,软引用,弱引用,虚引用 。

因为这里涉及到弱引用,简单说明下:

弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,该对象仅仅被弱引用关联,那么就会被回收。

当仅仅只有ThreadLocalMap中的Entry的key指向ThreadLocal的时候,ThreadLocal会进行回收的!!!

ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是Entry是强引用,那么Entry里面存储的Object,并没有办法进行回收,所以ThreadLocalMap 做了一些额外的回收工作。

ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用

想要避免内存泄露就要手动remove()掉

ThreadLocal的最佳实践!

ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是Entry是强引用,那么Entry里面存储的Object,并没有办法进行回收,所以ThreadLocalMap 做了一些额外的回收工作。

备注:很多时候,我们都是用在线程池的场景,程序不停止,线程基本不会销毁!!!

由于线程的生命周期很长,如果我们往ThreadLocal里面set了很大很大的Object对象,虽然set、get等等方法在特定的条件会调用进行额外的清理,但是ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是后续在也没有操作set、get等方法了。

所以最佳实践,应该在我们不使用的时候,主动调用remove方法进行清理。

这里把ThreadLocal定义为static还有一个好处就是,由于ThreadLocal有强引用在,那么在ThreadLocalMap里对应的Entry的键会永远存在,那么执行remove的时候就可以正确进行定位到并且删除!!!

最佳实践做法应该为:

抽象为:

try {

// 其它业务逻辑

} finally {

threadLocal对象.remove();

}

我对ThreadLocal的一些理解 内存泄露啥的相关推荐

  1. 使用ThreadLocal不当可能会导致内存泄露

    使用ThreadLocal不当可能会导致内存泄露 基础篇已经讲解了ThreadLocal的原理,本节着重来讲解下使用ThreadLocal会导致内存泄露的原因,并讲解使用ThreadLocal导致内存 ...

  2. ThreadLocal可能引起的内存泄露

    threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好 ...

  3. 一文搞懂ThreadLocal及相关的内存泄露问题

    首先,看一张整体的结构图,来帮助理解 什么是ThreadLocal ThreadLocal用于创建线程局部变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副 ...

  4. ThreadLocal是否会引发内存泄露的分析 good

    这篇文章,主要解决一下疑惑: 1. ThreadLocal.ThreadLocalMap中提到的弱引用,弱引用究竟会不会被回收? 2. 弱引用什么情况下回收? 3. JAVA的ThreadLocal和 ...

  5. ThreadLocal巨坑!内存泄露只是小儿科

    本文将会详细总结 ThreadLocal 容易用错的三个坑: 内存泄露 线程池中线程上下文丢失 并行流中线程上下文丢失 内存泄露 由于 ThreadLocal 的 key 是弱引用,因此如果使用后不调 ...

  6. c++ socket线程池原理_一篇文章看懂 ThreadLocal 原理,内存泄露,缺点以及线程池复用的值传递问题...

    编辑:业余草来源:https://www.xttblog.com/?p=4946 一篇文章看懂 ThreadLocal 原理,内存泄露,缺点以及线程池复用的值传递问题. ThreadLocal 相信不 ...

  7. java 内存泄漏场景_Java内存泄露的例子

    在定位JVM性能问题时可能会遇到内存泄露导致JVM OutOfMemory的情况,在使用Tomcat容器时如果设置了reloadable="true"这个参数,在频繁热部署应用时也 ...

  8. inputstreamreader未关闭会导致oom_ThreadLocal 一定会导致内存泄露?

    在面试的时候,ThreadLocal作为高并发常用工具经常会被问到.而面试官比较喜欢问的问题有以下两个: 1.ThreadLocal是怎么实现来保证每个线程的变量副本的. 2.ThreadLocal的 ...

  9. threadlocal内存泄露_深入理解 ThreadLocal

    前言 上篇文章 https://juejin.im/post/5d712cedf265da03ea5a9ecf 中提到了获取线程的 Looper 是通过 ThreadLocal 来实现的: publi ...

最新文章

  1. STM32:从菜鸟到牛人就是如此简单!为了学习单片机而去学习单片机的思路是不对的
  2. POJ 2114 - Boatherds
  3. OpenGL之正背面剔除、深度测试与多边形偏移
  4. C. Commentator problem
  5. mysql 设置时区,【MySQL】修改时区设置
  6. Build Roads
  7. java缩写_Java学习-Java缩写词的意思
  8. Doule类型转成十六进制查看
  9. MyEclipse Maven 警告: Failed to scan JAR [file:/C:/xxxxx.jar] from WEB-INF/lib
  10. 折腾Java设计模式之访问者模式
  11. 计算机游戏制作英文要学好吗,有关怎么不沉迷于电脑游戏的英语作文你的同学MICHAEL沉迷于电脑游戏中,影响了学习.作为好朋友,你要怎么帮助他?写一篇80词左右的英语作文....
  12. 【优化算法】静电放电优化算法(ESDA)【含Matlab源码 1439期】
  13. 一元云购 java源码
  14. android微信朋友圈相册背景,微信朋友圈相册背景多大尺寸合适
  15. bootloader 解析
  16. 文件或目录损坏且无法读取的解决办法
  17. 后退一步 小程序_微信小程序:如何利用navigateBack退出小程序
  18. 在Geany里配置python3的方法!!!含window10下载Geany过程
  19. 剑麻的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  20. C语言:最大公约数。

热门文章

  1. 【031】◀▶ 一些心得体会总结
  2. 5种方式将机器学习带到Java、Python以及Go等编程语言
  3. 在脚本中, 使用sqlite3检查android程序生成的数据库是否OK
  4. 蓝桥杯 PREV-27 历届试题 蚂蚁感冒
  5. 蓝桥杯 ADV-141 算法提高 判断名次
  6. 蓝桥杯 ALGO-39 算法训练 数组排序去重
  7. 计算机网络 时延、发送时延、传输时延、处理时延、排队时延、时延带宽积
  8. 【数据库原理】滨江学院姜青山 期末试卷知识点笔记整理 南京信息工程大学
  9. java设置只有一行表格,为什么我的表格插入一行后 样式都变了?是因为没有设置css吗?如果在java函数中插入的td.innerHTML = input type='text'/,可以设置样...
  10. Choerodon猪齿鱼敏捷管理实践(一)——需求管理