来源 | oschina 社区
作者 | 京东云开发者-京东物流 闫鹏勃
1 什么是 threadlocal?
threadlocal 是一个关于创建线程局部变量的类。
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用 threadlocal 创建的变量只能被当前线程访问,其他线程则无法访问和修改。threadlocal 在设计之初就是为解决并发问题而提供一种方案,每个线程维护一份自己的数据,达到线程隔离的效果。
2 有什么作用?
2.1 set once,get everywhere
在现在的系统设计中,前后端分离已基本成为常态,分离之后如何获取用户信息就成了一件麻烦事,通常在用户登录后, 用户信息会保存在 session 或者 token 中。这个时候,我们如果使用常规的手段去获取用户信息会很费劲,拿 session 来说,我们要在接口参数中加上 httpservletrequest 对象,然后调用 getsession 方法,且每一个需要用户信息的接口都要加上这个参数,才能获取 session,这样实现就很麻烦了。 在实际的系统设计中,我们肯定不会采用上面所说的这种方式,而是使用 threadlocal,我们会选择在拦截器的业务中, 获取到保存的用户信息,然后存入 threadlocal,那么当前线程在任何地方如果需要拿到用户信息都可以使用 threadlocal 的 get () 方法 (异步程序中 threadlocal 是不可靠的)
2.2 线程安全,空间换时间
在 spring 的 web 项目中,我们通常会将业务分为 controller 层,service 层,dao 层, 我们都知道 @autowired 注解默认使用单例模式,那么不同请求线程进来之后,由于 dao 层使用单例,那么负责数据库连接的 connection 也只有一个, 如果每个请求线程都去连接数据库,那么就会造成线程不安全的问题,spring 是如何解决这个问题的呢? 在 spring 项目中 dao 层中装配的 connection 肯定是线程安全的,其解决方案就是采用 threadlocal 方法,当每个请求线程使用 connection 的时候, 都会从 threadlocal 获取一次,如果为 null,说明没有进行过数据库连接,连接后存入 threadlocal 中,如此一来,每一个请求线程都保存有一份 自己的 connection。于是便解决了线程安全问题
3 threadlocal 实战应用
3.1 ehr 中的使用
在登录拦截器中将用户信息写入,后续使用时方便取值
3.2 分页插件 pagehelper 中的应用
3.3 aopcontext
4 源码解读
你是否有这样的疑惑?为什么可以直接拿到?对象存放在哪里?存在什么问题?
4.1 get 方法
在 get () 方法中也会获取到当前线程的 threadlocalmap,如果 threadlocalmap 不为 null,则把获取 key 为当前 threadlocal 的值;否则调用 setinitialvalue () 方法返回初始值,并保存到新创建的 threadlocalmap 中。
4.2 set 方法
调用 set 时,直接调用 set (t value) 方法中,首先获取当前线程,然后在获取到当前线程的 threadlocalmap,如果 threadlocalmap 不为 null,则将 value 保存到 threadlocalmap 中,并用当前 threadlocal 作为 key;否则创建一个 threadlocalmap 并给到当前线程,然后保存 value。 threadlocalmap 相当于一个 hashmap,是真正保存值的地方
map 的 set,如果 map 为空,则创建一个
4.3 initialvalue () 方法
initialvalue () 是 threadlocal 的初始值,默认返回 null,子类可以重写改方法,用于设置 threadlocal 的初始值。
4.4 remove () 方法
threadlocal 还有一个 remove () 方法,用来移除当前 threadlocal 对应的值。同样也是同过当前线程的 threadlocalmap 来移除相应的值。
getmap 拿到了什么?
在 set,get,initialvalue 和 remove 方法中都会获取到当前线程,然后通过当前线程获取到 threadlocalmap,如果 threadlocalmap 为 null,则会创建一个 threadlocalmap,并给到当前线程
此处 t 是 thread,直接可以 “点” 拿到这个 map
每个 thread 对象内部都维护了一个 threadlocalmap 这样一个 threadlocal 的 map,可以存放若干个 threadlocal
在使用 threadlocal 类型变量进行相关操作时,都会通过当前线程获取到 threadlocalmap 来完成操作。每个线程的 threadlocalmap 是属于线程自己的,threadlocalmap 中维护的值也是属于线程自己的。这就保证了 threadlocal 类型的变量在每个线程中是独立的,在多线程环境下不会相互影响。
5 使用注意事项
1)有可能导致内存泄漏,使用完毕后,需要 remove 在 threadlocalmap 的 set (),get () 和 remove () 方法中,都有清除无效 entry 的操作,这样做是为了降低内存泄漏发生的可能。
entry 中的 key 使用了弱引用的方式,这样做是为了降低内存泄漏发生的概率,但不能完全避免内存泄漏。
假设 entry 的 key 没有使用弱引用的方式,而是使用了强引用:由于 threadlocalmap 的生命周期和当前线程一样长,那么当引用 threadlocal 的对象被回收后,由于 threadlocalmap 还持有 threadlocal 和对应 value 的强引用,threadlocal 和对应的 value 是不会被回收的,这就导致了内存泄漏。所以 entry 以弱引用的方式避免了 threadlocal 没有被回收而导致的内存泄漏,但是此时 value 仍然是无法回收的,依然会导致内存泄漏。
threadlocalmap 已经考虑到这种情况,并且有一些防护措施:在调用 threadlocal 的 get (),set () 和 remove () 的时候都会清除当前线程 threadlocalmap 中所有 key 为 null 的 value。这样可以降低内存泄漏发生的概率。所以我们在使用 threadlocal 的时候,每次用完 threadlocal 都调用 remove () 方法,清除数据,防止内存泄漏。
2)使用线程池时,父子线程传递慎用,因为初始化时机为线程创建时
3)针对 2 有什么方案可以解决?
transmittablethreadlocal
源码地址:https://github.com/alibaba/transmittable-thread-local
CES2017各种重磅新品及技术,感受不一样的精彩!
陶瓷覆铜板在光伏发电系统的重要作用
到底什么是TCP/IP协议栈,看完这篇你就明白!
LO驱动器表面贴装和LO驱动器裸模/模块的区别
小型UPS电源电路
ThreadLocal源码解析及实战应用
中国移动发布了2019年至2020年一体化电源产品集中采购招标公告
蔬菜农药残留速测仪功能特点的详细说明
对于物联网无线充电技术你了解多少
蔚来成为中国互联网造车标杆 蔚来ES8堪称最贵新能源车
NVIDIA打造中国特供GPU:居然只砍了一刀!
艾德克斯对时下较先进领域发布一系列先进仪器
使用橡皮擦清洁PCB板会不会导致ESD的产生?
In-Circuit Programming for the
HomePod :用真实去营造出梦幻的场景
LDO线性稳压器的工作原理及选型参数
ReactOS开源系统最新进展公布,支持64位Win应用
一站式解决方案促进无人机产业高质量可持续发展
FPC片状处理是怎样的
MySQL字符集不一致导致索引失效的案例分析