文章目录
一、初识线程本地变量ThreadLocal
1.1、ThreadLocal是什么
ThreadLocal是每个线程单独特有的一个本地变量,用于存放当前线程产生数据的副本,存储到这个变量中的数据可跨方法以及跨服务进行传递,例如:Spring中事务的控制,使用了ThreadLocal确保了当前操作都是属于同一个数据库连接,不然会导致事务失效,再例如:在使用MDC日志追踪框架时,其实也是用到ThreadLocal进行操作
1.2、ThreadLocal内部存储数据结构
在Thread类中,定义的一个ThreadLocalMap集合容器:
接着点进ThreadLocal中会发现,这个Map维护了一个Entry数组,每个Entry(且ThreadLocal被弱引用关键字修饰)是以ThreadLocal对象为key,要存储的值为value:
所以,结合上面的代码图,我们可以大致的去构思出ThreadLocal内部结构图示:
1.3、ThreadLocal使用
ThreadLocal类接口中有几个方法,如下:
- **void set(Object value) **:设置当前线程的线程局部变量的值;
- public Object get():返回当前线程所对应的线程局部变量;
- public void remove():将当前的线程对应的局部变量移除,目的是为了减少内存的占用;
- protected Object initialValue():该方法是返回当前线程对应的线程局部变量的初始值,是一个被protect修饰的方法,目的是为让子类可重写覆盖,这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null
1.4、ThreadLocal内部结构hash冲突解决方案
解决hash冲突发主要有以下几种:
- 链地址法:这种方法是hashMap采用的方法,如果出现hash冲突,直接在链表后插入即可,如下图:
- 再has法:这种方法是同时构造多个不同的哈希函数:Hi=RH1(key) i=1,2,…,k当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。
- 开放寻址发
3.1、线性探测再散列:如果当前hash到的位置以及有元素的,再往紧接着的下一个进行判断,以此类推,ThreadLocal是采用的这种方式来解决hash冲突,如下图示:
3.2、二次探测再散列:如果当前hash到的位置以及有元素的,再往1、2、3次方位置进行判断,以此类推,如下图示:
3.3、伪随机:顾名思义就是随机产生一个增量位移,如下图:
二、ThreadLocal产生内存泄漏分析
内部结构图,我们可以大致构思出对象在堆和栈上的分布情况,如下图示:
如上图示,如果当前局部的方法执行完毕了,但线程未结束,那ThreadLocal这个局部变量就会出栈,相关的引用也随之去,那堆中,创建出的ThreadLocal对象只有Entry的弱引用指向了,如果发生GC,那ThreadLocal对象是直接被收回的,这时,Entry对象的key就会为null,那这个数据就无法访问了,就属于垃圾对象了,特别是线程池环境下,越积越多,就产生了内存泄漏,解决方案其实就是在局部方法执行完毕之后,记得调用当前ThreadLocal对应的remove方法即可;