一文搞懂 ThreadLocal 原理
ThreadLocal 是什么
在多线程编程中,经常会遇到需要在不同线程中共享数据的情况。通常情况下,为了保证线程安全,我们需要使用锁或其他同步机制。然而,有些情况下,我们希望在每个线程中都有一份独立的数据副本,这就是 ThreadLocal 派上用场的地方。
ThreadLocal 翻译过来就是线程本地,也就是本地线程变量,意思是 ThreadLocal 中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal 提供了一种机制,允许我们为每个线程创建独立的变量,每个线程都可以独立访问自己的变量,而不会干扰其他线程的数据。ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,各个线程间互不影响,从而实现线程安全。
ThreadLocal 的原理
ThreadLocal 的原理涉及到两个重要概念:ThreadLocal 实例和 ThreadLocalMap。
1. ThreadLocal 实例
每个 ThreadLocal 对象实际上是一个容器,用于存储线程本地的变量副本。每个线程都可以拥有自己的 ThreadLocal 实例,这些实例可以存储不同的数据,互相之间互不影响。
2. ThreadLocalMap
ThreadLocalMap 是 ThreadLocal 的底层数据结构,它是一个哈希表。每个线程都有一个与之相关联的 ThreadLocalMap,用于存储该线程所拥有的 ThreadLocal 实例以及对应的值。ThreadLocalMap 中的键是 ThreadLocal 实例,值是该线程对应 ThreadLocal 实例的变量副本。
当我们调用 ThreadLocal 的 set()方法设置值时,实际上是在当前线程的 ThreadLocalMap 中以 ThreadLocal 实例为键,将值存储在对应的位置。而调用 get()方法时,则是从当前线程的 ThreadLocalMap 中根据 ThreadLocal 实例获取对应的值。
ThreadLocal 怎么用,应用场景
从结果可以看到,每一个线程都有各自的值,并且互不影响。
应用场景
用户访问之后,在本地线程保存用户的身份信息,在本次访问过程中,可以随时获取用户的身份信息以验证身份。
在进行对象跨层传递的时候,使用 ThreadLocal 可以避免多次传递,打破层次间的约束。
数据库连接管理:每个线程可以拥有自己的数据库连接,避免了线程之间的数据库连接混淆。
用户身份管理:在 Web 应用中,可以将用户身份信息存储在 ThreadLocal 中,以便在整个请求处理过程中方便地访问。
事务管理:将事务状态存储在 ThreadLocal 中,确保每个线程都能独立管理自己的事务状态。
ThreadLocal 源码分析
先看一下 ThreadLocal 和 Thread 的关系
Thread 类中有一个 threadLocals 属性,是 ThreadLocal 内部类 ThreadLocalMap 类型的变量,ThreadLocalMap 可以看作是一个 HashMap,其内部有一个内部类为 Entry,继承了 WeakReference<ThreadLocal<?>>,是一个弱引用。Entry 的 key 是 ThreadLocal<?>,value 是 Object 类型的值。
大致了解了 Thread 和 ThreadLocal 的关系之后,看一下 Thread Local 的源码:我们只要看其主要的几个方法,就可以完全了解 ThreadLocal 的原理了。
set 方法
get 方法
remove 方法
总结:
每个 Thread 维护着一个 ThreadLocalMap 的引用
ThreadLocalMap 是 ThreadLocal 的内部类,用 Entry 来进行存储
ThreadLocal 创建的副本是存储在自己的 threadLocals 中的,也就是自己的 ThreadLocalMap。
ThreadLocalMap 的键为 ThreadLocal 对象,而且可以有多个 threadLocal 变量,因此保存在 map 中
在进行 get 之前,必须先 set,否则会报空指针异常,当然也可以初始化一个,但是必须重写 initialValue()方法。
ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。
注意事项
虽然 ThreadLocal 在某些情况下非常有用,但过度使用它也可能导致内存泄漏问题。因为 ThreadLocalMap 中的数据只有在线程结束时才会被释放,如果没有正确地清理 ThreadLocal 实例,就可能会导致无限制的数据积累。
另外,ThreadLocal 不适合在并发量非常大的情况下使用,因为每个线程都会创建自己的变量副本,可能会导致内存消耗过大。
ThreadLocal 内存泄漏问题
在上面的代码中,我们可以看出,当前 ThreadLocal 的引用 k 被传递给 WeakReference 的构造函数,所以 ThreadLocalMap 中的 key 为 ThreadLocal 的弱引用。
如果当前线程一直存在且没有调用该 ThreadLocal 的 remove 方法,如果这个时候别的地方还有对 ThreadLocal 的引用,那么当前线程中的 ThreadLocalMap 中会存在对 ThreadLocal 变量的引用和 value 对象的引用,是不会释放的,会造成内存泄漏。
ThreadLocalMap 中的 Entry 的 key 使用的是 ThreadLocal 对象的弱引用,在没有其他地方对 ThreadLoca 依赖,ThreadLocalMap 中的 ThreadLocal 对象就会被回收掉,但是对应的 value 值不会被回收,这个时候 Map 中就可能存在 key 为 null 但是 value 不为 null 的项,也会造成内存泄漏。
小结
ThreadLocal 是一种多线程编程的工具,可以帮助我们在多线程环境中管理线程本地的变量。它通过 ThreadLocal 实例和 ThreadLocalMap 的组合实现了这一功能。
使用 ThreadLocal 时需要注意内存泄漏和性能问题,确保合理使用。
使用完 ThreadLocal 后,一定执行 remove 操作,避免出现内存泄漏情况。
版权声明: 本文为 InfoQ 作者【树上有只程序猿】的原创文章。
原文链接:【http://xie.infoq.cn/article/f64a97a63986d1f970f29148e】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论