写点什么

ThreadLocal 源码解析及实战应用

  • 2023-01-09
    北京
  • 本文字数:2213 字

    阅读完需:约 7 分钟

ThreadLocal源码解析及实战应用
作者:京东物流 闫鹏勃

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


详解:https://www.jianshu.com/p/e0774f965aa3

发布于: 刚刚阅读数: 4
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
ThreadLocal源码解析及实战应用_Java_京东科技开发者_InfoQ写作社区