对ThreadLocal的一点了解

简介: ThreadLocal是线程变量,它为每个线程提供单独的存储空间。其主要作用是做线程间的数据隔离,也可以用于在同一个线程间方便地进行数据共享。(对于多线程资源共享,加锁机制采用“时间换空间”,ThreadLocal采用“空间换时间”)

ThreadLocal是线程变量,它为每个线程提供单独的存储空间。其主要作用是做线程间的数据隔离,也可以用于在同一个线程间方便地进行数据共享。(对于多线程资源共享,加锁机制采用“时间换空间”,ThreadLocal采用“空间换时间”)

原理:(自行参考ThreadLocal和Thread源码)

1、Thread类有两个成员变量threadLocals和inheritableThreadLocals,均为ThreadLocal类的静态内部类ThreadLocalMap类型的变量,ThreadLocalMap是一个类似于HashMap类型的容器类,默认情况下两个变量都为null,只有第一次调用ThreadLocal的get或set方法时才会创建。

2、threadLocal的set方法:获取当前线程t。获取t的成员变量threadLocals(ThreadLocal.ThreadLocalMap)并命名为map。map不为空则存入一个entry(Entry为ThreadLocal的静态内部类ThreadLocalMap的静态内部类并继承自WeakReference),entry的key为当前threadLocal(this)。map为空则初始化后存入entry。ThreadLocalMap和HashMap相像,它也靠计算key的哈希值来确定entry存放位置和判断两个entry是否相同,相同则会覆盖旧值。

区别在于:

ThreadLocalMap未实现Map接口,底层结构为Entry数组,且Entry继承自WeakReference(弱引用)。

HashMap的实现了Map接口,底层结构在jdk8后是数组、链表、红黑树。

HashMap解决哈希冲突的方式是在数组冲突的位置形成链表。ThreadLocalMap则是遇见冲突就按顺序寻找下一个位置存储。

3、threadLocal的get方法:获取当前线程成员变量threadLocals中当前threadLocal作为key对应的值,不存在则返回空。

4、threadLocal的remove方法:清除。remove方法不按预期调用时可能导致内存泄漏和各种bug。

附:

弱引用:只具有弱引用的对象拥有更短暂的生命周期,在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

ThreadLocal在没有外部强引用时会在GC时被回收,如果创建ThreadLocal的线程一直运行(如使用线程池时可能发生),则因Entry对象的value得不到回收从而发生内存泄露。

threadLocals和inheritableThreadLocals:创建线程时,构造函数中可以指定inheritThreadLocals参数来决定是否继承父线程的inheritableThreadLocals,从而实现父子线程间的数据传递。

使用场景:

Spring中,@Transaction的事务通过jdbc事务实现,管理某个事务时需要使用同一个数据库连接,该连接通过ThreadLocal来传递。采用ThreadLocal可以保证单个线程中的数据库操作使用的是同一个数据库连接。这种方式使得业务层使用事务时不需要感知和管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。

SimpleDataFormat在多线程共享使用时存在线程安全问题,除了使用DateTimeFormatter替代外,可以使用ThreadLocal包装SimpleDataFormat,再调用initialValue让每个线程有一个SimpleDataFormat的副本,从而解决线程安全问题,也提高了性能。

项目中经常存在在一个线程内横跨多个方法调用且需要传递一个对象(也就是上下文,可以理解为状态,比如用户身份信息)的场景,这时存在过渡传参的问题,如果给每个方法都增加context参数非常麻烦且不优雅。这时很适合使用ThreadLocal存放需要传递的参数。

PageHelper使用ThreadLocal支持线程隔离,保证并发访问时每个线程能够访问到各自独立的分页信息,并使用finally清除存储在ThreadLocal中的对象以确保下一次请求不会受到旧数据影响。(参考PageMethod、PageInterceptor、PageHelper)。

注意事项:PageHelper使用ThreadLocal保存分页参数,分页参数和线程是绑定的。因此需要保证PageHelper.startPage()调用后紧跟查询方法,否则可能导致非预期的方法进行分页。

还有很多场景的cookie,session等数据隔离是通过ThreadLocal实现的,比如spring对HttpServletRequest的管理(参考RequestContextHolder、ServletRequestAttributes)。

相关文章
|
算法 Python
Python算法——二叉树遍历
Python算法——二叉树遍历
190 0
|
8月前
|
机器学习/深度学习 人工智能 弹性计算
阿里云AI服务器价格表_GPU服务器租赁费用_AI人工智能高性能计算推理
阿里云AI服务器提供多种配置,包括CPU+GPU、FPGA等,适用于人工智能、机器学习和深度学习等计算密集型任务。本文整理了阿里云GPU服务器的优惠价格,涵盖NVIDIA A10、V100、T4等型号,提供1个月、1年和1小时的收费明细。具体规格如A10卡GN7i、V100-16G卡GN6v等,适用于不同业务场景,详情见官方页面。
755 11
|
11月前
|
传感器 运维 安全
物联网:物联网卡的优势
物联网卡(IoT SIM卡)作为连接物联网设备与互联网的桥梁,具备一系列显著优势,这些优势使得物联网卡成为推动物联网(IoT)应用发展的关键要素。以下是物联网卡优势的操作层面解析:
|
10月前
昇腾910A部署Qwen2-7B教程
Qwen2-7BS适配昇腾910A教程。
|
安全 Linux C++
C++多线程
C++多线程
97 1
|
JavaScript 前端开发
JavaScript 优化判断条件
JavaScript 优化判断条件
|
安全 开发工具 Python
[新手向视频]新版PyCharm创建项目为什么会有问题
而 PyCharm 在2017年的新版本中,对新建项目的配置增加了一点小功能。这些功能很有帮助,但却会让刚刚接触开发的新手困惑。最近已经连续有好几个同学问到这个问题,所以今天专门来演示一下。
|
存储 传感器
手持VH501TC采集仪如何处理监测到的数据
在实时数据显示窗口, 长按【存储】按键即可保存当前显示的传感数据,当听到蜂鸣器提示后表示存储完成,同时屏幕底部的已保存数量值自动加 1。 VH501TC 支持对传感器进行编号的功能,以便在导出数据时区分出某条数据对应哪个传感器。传感器编号需要在保存数据操作前设置,具体方法为:短按【电源/上一个】或者【存储/下一个】按键,屏幕底部数据存储指示区域会显示传感器编号。在数据保存前,还应确认屏幕显示的实时日期、时间是否正确,数据保存时会将时间信息、传感器编号以及屏幕显示的频率、频模、温度、信号质量、电压、电流一并保存为一条数据。 若外接了 U 盘,保存数据操作会自动将本条数据进行同步存储。
手持VH501TC采集仪如何处理监测到的数据
|
存储 自然语言处理 监控
开放下载!《阿里云实时数仓Hologres技术揭秘2.0》
《阿里云实时数仓Hologres技术揭秘2.0》电子书重磅来临,介绍实时数仓Hologres核心技术原理与优势,融合实时数仓最热门场景的最佳实践。
45114 1
开放下载!《阿里云实时数仓Hologres技术揭秘2.0》
|
关系型数据库 MySQL 分布式数据库
《PolarDB MySQL引擎重磅功能及产品能力盛大发布》电子版地址
PolarDB MySQL引擎重磅功能及产品能力盛大发布.ppt
140 0
《PolarDB MySQL引擎重磅功能及产品能力盛大发布》电子版地址