ThreadLocal源码解析及实战应用

描述

来源 | 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 中的使用

在登录拦截器中将用户信息写入,后续使用时方便取值

springspring

3.2 分页插件 PageHelper 中的应用

springspring

3.3 AopContext

spring

4 源码解读

你是否有这样的疑惑?为什么可以直接拿到?对象存放在哪里?存在什么问题?

4.1 get 方法

在 get () 方法中也会获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则把获取 key 为当前 ThreadLocal 的值;否则调用 setInitialValue () 方法返回初始值,并保存到新创建的 ThreadLocalMap 中。 spring

4.2 set 方法

调用 set 时,直接调用 set (T value) 方法中,首先获取当前线程,然后在获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则将 value 保存到 ThreadLocalMap 中,并用当前 ThreadLocal 作为 key;否则创建一个 ThreadLocalMap 并给到当前线程,然后保存 value。 ThreadLocalMap 相当于一个 HashMap,是真正保存值的地方
map 的 set,如果 map 为空,则创建一个

springspring

4.3 initialValue () 方法

initialValue () 是 ThreadLocal 的初始值,默认返回 null,子类可以重写改方法,用于设置 ThreadLocal 的初始值。 spring

4.4 remove () 方法

ThreadLocal 还有一个 remove () 方法,用来移除当前 ThreadLocal 对应的值。同样也是同过当前线程的 ThreadLocalMap 来移除相应的值。 spring

getMap 拿到了什么?


在 set,get,initialValue 和 remove 方法中都会获取到当前线程,然后通过当前线程获取到 ThreadLocalMap,如果 ThreadLocalMap 为 null,则会创建一个 ThreadLocalMap,并给到当前线程 spring

此处 t 是 Thread,直接可以 “点” 拿到这个 map
每个 Thread 对象内部都维护了一个 ThreadLocalMap 这样一个 ThreadLocal 的 Map,可以存放若干个 ThreadLocal
spring

在使用 ThreadLocal 类型变量进行相关操作时,都会通过当前线程获取到 ThreadLocalMap 来完成操作。每个线程的 ThreadLocalMap 是属于线程自己的,ThreadLocalMap 中维护的值也是属于线程自己的。这就保证了 ThreadLocal 类型的变量在每个线程中是独立的,在多线程环境下不会相互影响。

5 使用注意事项

1)有可能导致内存泄漏,使用完毕后,需要 remove 在 ThreadLocalMap 的 set (),get () 和 remove () 方法中,都有清除无效 Entry 的操作,这样做是为了降低内存泄漏发生的可能。


Entry 中的 key 使用了弱引用的方式,这样做是为了降低内存泄漏发生的概率,但不能完全避免内存泄漏。 spring


假设 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)使用线程池时,父子线程传递慎用,因为初始化时机为线程创建时

spring

3)针对 2 有什么方案可以解决?
TransmittableThreadLocal
源码地址:https://github.com/alibaba/transmittable-thread-local  

审核编辑:汤梓红

 
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分