前言上次有个小伙伴问我,说他面试的时候,被问到ThreadLocal内存溢出问题,没有回答出来;那我们今天就来了解一下ThreadLocal 。
ThreadLocal介绍多线程在访问同一个变量时会产生线程安全问题,ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示
文章插图
ThreadLocal使用开启两个线程,在每个线程内部设置了本地变量的值,然后调用print方法打印当前本地变量的值 。如果在打印之后调用本地变量的remove方法会删除本地内存中的变量,代码如下所示
文章插图
执行结果
【来说说ThreadLocal内存溢出问题】
thread1 :localVar1thread2 :localVar2thread1 after remove : nullthread2 after remove : null
上面代码能够看出,虽然2个线程都使用了ThreadLocal localVar变量,但是线程里面的变量互相不影响 。ThreadLocal原理我们来看看ThreadLocal是怎么实现的?我们来看一下源码
set源码
文章插图
在上面的代码中,(2)处调用getMap方法获得当前线程对应的threadLocals,该方法代码如下
ThreadLocalMap getMap(Thread t) {return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上}
如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前ThreadLocal引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建threadLocals,该方法如下所示void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}
createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中 。get源码
文章插图
在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量 。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null
remove实现
文章插图
remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量
溢出分析每个线程内部有一个名为threadLocals的成员变量,该变量的类型为
ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值 。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉 。
看下图:
文章插图
番外篇1、ThreadLocalMap 内部 Entry 中 key 使用的是对 ThreadLocal 对象的弱引用,这是为了避免内存泄露,因为如果是强引用,那么即使其他地方没有对 ThreadLocal 对象的引用,ThreadLocalMap 中的 ThreadLocal 对象还是不会被回收,而如果是弱引用则这时候 ThreadLocal引用是会被回收掉的 。
2、但是对于的 value 还是不能被回收,这时候 ThreadLocalMap 里面就会存在 key 为 null 但是 value 不为 null 的 entry 项,虽然 ThreadLocalMap 提供了 set,get,remove 方法在一些时机下会对这些 Entry 项进行清理,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露,所以在使用完毕后即使调用 remove 方法才是解决内存泄露的最好办法 。
3.线程池里面设置了 ThreadLocal 变量一定要记得及时清理,因为线程池里面的核心线程是一直存在的,如果不清理,那么线程池的核心线程的 threadLocals 变量一直会持有 ThreadLocal 变量 。
推荐阅读
- SpringBoot整合定时器:定时任务不再硬编码,动态定时刷起来
- Windows11来袭,10步带你看win11的安装全过程
- 如何让你的代码写起来更加优雅?Lamda表达式助你起飞
- 短发|新时尚短发来了,立体有型美美哒,喜欢别错过
- 你吃什么食物来治疗便秘?便秘吃什么好
- 乾隆八位皇贵妃,康熙的贵妃
- 翡翠|危料的含义是什么?与翡翠有着怎样的联系?一起来探讨下
- I/O多路复用,从来没遇到过这么明白的文章
- 前端如何解决跨域问题?
- 玉米萝卜丝饼的做法