您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
ThreadLocal源码解析及实战应用
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
ThreadLocal源码解析及实战应用
自猿其说Tech
2022-08-11
IP归属:未知
51160浏览
计算机编程
### 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中的使用 在登录拦截器中将用户信息写入,后续使用时方便取值 ![](//img1.jcloudcs.com/developer.jdcloud.com/7e00ddd7-b183-464c-a673-2077e295f2ce20220811182917.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/819646f9-3430-460e-a3c7-dfe19b4ec4bb20220811182925.png) #### 3.2 分页插件PageHelper中的应用 ![](//img1.jcloudcs.com/developer.jdcloud.com/f99775bd-a9a5-498f-8458-6e41aba6530720220811182938.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/c6603b7c-59f5-4309-9176-a689124534b820220811182944.png) #### 3.3 AopContext ![](//img1.jcloudcs.com/developer.jdcloud.com/7799a9f8-bbaa-47a8-818b-ce379fc248c520220811183050.png) ### 4 源码解读 你是否有这样的疑惑?为什么可以直接拿到?对象存放在哪里?存在什么问题? #### 4.1 get方法 在 get() 方法中也会获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则把获取 key 为当前 ThreadLocal 的值;否则调用 setInitialValue() 方法返回初始值,并保存到新创建的 ThreadLocalMap 中。 ![](//img1.jcloudcs.com/developer.jdcloud.com/574dc94f-9bd3-4f02-ae55-900ffc48117920220811183125.png) #### 4.2 set方法 调用set时,直接调用set(T value) 方法中,首先获取当前线程,然后在获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则将 value 保存到 ThreadLocalMap 中,并用当前 ThreadLocal 作为 key;否则创建一个 ThreadLocalMap 并给到当前线程,然后保存 value。 ThreadLocalMap 相当于一个 HashMap,是真正保存值的地方 map的set,如果map为空,则创建一个 ![](//img1.jcloudcs.com/developer.jdcloud.com/ca281752-c6db-4875-8be2-b72ebb91649f20220811183217.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/80b724a2-e6a1-438a-9b6f-3a4af9856c7e20220811183227.png) #### 4.3 initialValue() 方法 initialValue() 是 ThreadLocal 的初始值,默认返回 null,子类可以重写改方法,用于设置 ThreadLocal 的初始值。 ![](//img1.jcloudcs.com/developer.jdcloud.com/115424c2-9f40-4df5-b34a-ad5b9c9fa31220220811183244.png) #### 4.4 remove() 方法 ThreadLocal 还有一个 remove() 方法,用来移除当前 ThreadLocal 对应的值。同样也是同过当前线程的 ThreadLocalMap 来移除相应的值。 ![](//img1.jcloudcs.com/developer.jdcloud.com/cf529de4-42eb-49a5-8855-3d3f85389ace20220811183305.png) getMap拿到了什么? 在 set,get,initialValue 和 remove 方法中都会获取到当前线程,然后通过当前线程获取到 ThreadLocalMap,如果 ThreadLocalMap 为 null,则会创建一个 ThreadLocalMap,并给到当前线程 ![](//img1.jcloudcs.com/developer.jdcloud.com/10198e43-932e-4e54-934e-96ccdcfd4b8020220811183323.png) 此处t是Thread,直接可以“点”拿到这个map 每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal ![](//img1.jcloudcs.com/developer.jdcloud.com/c2757d4e-5541-4a78-907d-9d0e21221e8420220811183335.png) 在使用 ThreadLocal 类型变量进行相关操作时,都会通过当前线程获取到 ThreadLocalMap 来完成操作。每个线程的 ThreadLocalMap 是属于线程自己的,ThreadLocalMap 中维护的值也是属于线程自己的。这就保证了 ThreadLocal 类型的变量在每个线程中是独立的,在多线程环境下不会相互影响。 ### 5 使用注意事项 **1)有可能导致内存泄漏,使用完毕后,需要remove** 在 ThreadLocalMap 的 set(),get() 和 remove() 方法中,都有清除无效 Entry 的操作,这样做是为了降低内存泄漏发生的可能。 Entry 中的 key 使用了弱引用的方式,这样做是为了降低内存泄漏发生的概率,但不能完全避免内存泄漏。 ![](//img1.jcloudcs.com/developer.jdcloud.com/bcb76c84-30d3-4284-8e42-972cdec983a520220811183401.png) 假设 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)使用线程池时,父子线程传递慎用,因为初始化时机为线程创建时** ![](//img1.jcloudcs.com/developer.jdcloud.com/8788c5a3-a70f-42f0-b817-061927c1aac720220811183425.png) **3)针对2有什么方案可以解决?** TransmittableThreadLocal 源码地址: https://github.com/alibaba/transmittable-thread-local 详解:https://www.jianshu.com/p/e0774f965aa3 ------------ ###### 自猿其说Tech-JDL京东物流技术与数据智能部 ###### 作者:闫鹏勃
原创文章,需联系作者,授权转载
上一篇:JDL环境下windows系统自动部署解决方案
下一篇:浅谈Airtest图像识别原理
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号