探索ThreadLocal
2020/2/24 17:02:44
本文主要是介绍探索ThreadLocal,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
学 无 止 境 , 与 君 共 勉 。
特点
ThreadLocal
是一个线程内部的变量,只在本线程中使用,隔离其他线程ThreadLocal
内部维护了一个ThreadLocalMap
Thread
内部引用了ThreadLocalMap
ThreadLocalMap
可以保存键值对,但是一个
ThreadLocal
只能保存一个值,并且各个线程数据互不干扰ThreadLocalMap
存储时的key
永远为当前的ThreadLocal
ThreadLocalMap
存储时的key
是弱引用的
ThreadLocalMap
每个ThreadLocal
只能存储一个数据,如果需要存储多个值的话,可以定义多个ThreadLocal
。ThreadLocal
在内部维护了一个ThreadLocalMap
用来存储这些值。
ThreadLocalMap
并没有去实现Map
接口,它定义了一个Entry
数组,每个Entry
以<key,value>
的形式来保存值,其中key
为当前ThreadLocal
本身,value
为要保存的值。
注意
Entry
继承了WeakReference
,它的key
是弱引用的,会被垃圾回收掉,所以会存在key
为null
的情况
ThreadLocalMap
提供了三个方法:
- set():以当前
ThreadLocal
为key
存放值 - get():以当前
ThreadLocal
为key
获取存放的值 - remove():清除数据
set()方法
- 获取当前线程
Thread.currentThread()
- 获取当前线程的
ThreadLocalMap
- 判断
ThreadLocalMap
是否存在 - 不存在的,通过
createMap
,初始化一个ThreadLocalMap
,并赋值 - 存在的,将当前
ThreadLocal
作为key
,进行插入操作:- 通过ThreadLocal的哈希值获取要插入的位置
- 如果当前位置的Entry为空,直接在该位置初始化一个Entry对象来实现插入操作;
- 如果当前位置Entry的key和要设置的key相同,则覆盖原来的value
- 如果当前位置Entry的key为null:
- 循环获取下一位置
- 如果key和要设置的key相同,则覆盖这个位置的值,并将这个位置和要插入点的entry互换,清理key为null的值;
- 如果当前位置的Entry为null,退出循环,并在当前位置生成一个新的entry,并清理key为null的值;
get()方法
- 获取当前线程
Thread.currentThread()
- 从当前线程获取
ThreadLocalMap
- 判断
ThreadLocalMap
是否存在 - 不存在的调用
setInitialValue
进行初始化,并返回null
; - 存在的,则以当前
ThreadLocal
作为key
获取值:- 通过ThreadLocal的哈希值获取要获取值的位置
- 当前位置的entry存在且key相同的,直接返回当前值;
- 当前位置的entry存在且当前key为null的,执行清理重置方法
- 循环获取下一位置的entry进行对比,如果下一位置的key相同,则返回该值;
- 如果下一位置的entry为null,则说明该值不存在退出循环返回null;
remove()方法
清理当前ThreadLocal
对应的Entry
对象。并调用清理重置方法。
清理重置方法
- 处理区间:Entry数组当前位置到下一个不为null的Entry之间的数据;
- 清理key为null的Entry:value设为null,Entry设为null;
- 重置key不为null的Entry:
- 通过key的哈希值获取在Entry的数组的索引h;
- 如果h和当前Entry的索引不一致进行位置重置:
- 将当前位置的Entry设为null
- 从h处开始往后找到首个Entry为null的位置
- 将找到的新位置处的Entry设为原来的entry
Hash冲突
ThreadLocalMap
并没有实现Map
接口,它不是通过链表的形式去避免Hash冲突的,而是通过后移的方式去实现。set方法时,如果当前要存放的位置的key
和要设置的key
不一致,则会对下一个位置进行判断,直到找到key
相同或者为null
或者Entry
为null
的位置。
内存泄漏问题
在实际的项目中,我们的线程一般都是由线程池来管理的,线程会一直存在,ThreadLocalMap
的value就有可能得不到回收,发送内存泄漏。为了处理这一问题,ThreadLocal
的get()、set()方法都有可能会清除key
为null
的Entry
对象。安全起见,当我们使用完后应该手动调用remove()
方法清理掉数据。
用途
全局变量
某些数据比如用户ID,很可能在整条业务线上多个方法中都需要用到,如果通过方法参数的形式一层一层的传递下去,整体代码显得凌乱不优雅,这时可以通过ThreadLocal
的方式存储。通常可以通过AOP或者拦截器的方式进行赋值,执行完业务逻辑之后调用remove()
方法。
private final static ThreadLocal<UserInfo> TL_USER = new ThreadLocal<>(); TL_USER.set(userInfo); UserInfo userInfo = TL_USER.get(); TL_USER.remove(); 复制代码
独享对象
在实际项目中我们通常会将时间相关的方法写在一个工具类中,往往会用到SimpleDateFormat
进行格式化,它是线程不安全的。可以通过ThreadLocal来实现独享对象
private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")); 复制代码
日常求赞
创作不易,如果各位觉得有帮助,求点赞 支持
求关注
微信公众号: 俞大仙
这篇关于探索ThreadLocal的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-04el-table 开启定时器下,表格的选中状态会消失是什么原因-icode9专业技术文章分享
- 2024-10-03如何安装和初始化飞牛私有云 fnOS?-icode9专业技术文章分享
- 2024-10-03如何安装 App 并连接到飞牛 NAS?-icode9专业技术文章分享
- 2024-10-03如何安装飞牛 TV 并连接到影视服务器?-icode9专业技术文章分享
- 2024-10-03如何在PVE和ESXI上安装飞牛私有云 fnOS?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS安装系统异常情况处理-icode9专业技术文章分享
- 2024-10-03飞牛NAS如何创建存储空间?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS硬盘会自动休眠吗?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS如何安装飞牛影视和创建媒体库?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS如何为家人朋友开通影视账号?-icode9专业技术文章分享