本文代码基于Java8
前言
ThreadLocal
的官方API解释为:
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
ThreadLocal
提供了一种访问某个变量的特殊方式:访问到的变量属于当前线程,即保证每个线程的变量不一样,而同一个线程在任何地方拿到的变量都是一致的,这就是所谓的线程隔离。如果要使用
ThreadLocal
,通常定义为private static
类型,最好是定义为private static final
类型。
即 ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
ThreadLocal 类结构
ThreadLocal
类结构如下:
ThreadLocalMap
、SuppliedThreadLocal
是 ThreadLocal
内部类,且 SuppliedThreadLocal
继承自 ThreadLocal
, Entry
是 ThreadLocalMap
内部类。
1 | public class ThreadLocal<T> { |
ThreadLocalMap 实现
ThreadLocalMap
是一个定制的散列映射,仅适用于维护线程本地值。该类是包私有的,允许在 Thread
类中声明字段。在 ThreadLocal
类之外不做任何操作。为了帮助处理占用内存大和存活时间长的用法,哈希表 Entry
使用弱引用作为键。但是,由于不使用引用队列,因此只有当表开始耗尽空间时,才保证删除过时的条目。
每个线程可能有多个 ThreadLocal
,同一线程的各个ThreadLocal
存放于同一个 ThreadLocalMap
中。
1 | static class Entry extends WeakReference<ThreadLocal<?>> { |
ThreadLocalMap
具体实现:
1 | static class ThreadLocalMap { |
SuppliedThreadLocal 实现
SuppliedThreadLocal
是 JDK8 新增的内部类,只是扩展了 ThreadLocal
的初始化值的方法而已,允许使用 JDK8 新增的 Lambda 表达式赋值。需要注意的是,函数式接口 Supplier
不允许为 null。
1 | static final class SuppliedThreadLocal<T> extends ThreadLocal<T> { |
ThreadLocal 的基本方法
ThreadLocal
的基本方法包括取值、设置初始值、赋值、移除等
1 | // 取值 |
使用场景
变量有局部的还有全局的,局部变量没什么好说的,一涉及到全局,那自然就会出现多线程的安全问题,要保证多线程安全访问,不出现脏读脏写,那就要涉及到线程同步了。而 ThreadLocal
相当于提供了介于局部变量与全局变量中间的这样一种线程内部的全局变量。
当我们只想在本身的线程内使用的变量,可以用 ThreadLocal
来实现,并且这些变量是和线程的生命周期密切相关的,线程结束,变量也就销毁了。 ThreadLocal
不是为了解决线程间的共享变量问题的,如果是多线程都需要访问的数据,那需要用全局变量加同步机制。
- 线程中处理一个非常复杂的业务,可能方法有很多,那么,使用
ThreadLocal
可以代替一些参数的显式传递; - 在一些多线程的情况下,如果用线程同步的方式,当并发比较高的时候会影响性能,可以改为
ThreadLocal
的方式,例如高性能序列化框架 Kyro 就要用ThreadLocal
来保证高性能和线程安全; - 线程内上下文管理器、数据库连接等可以用到
ThreadLocal
; - 用来存储用户 Session。Session 的特性很适合
ThreadLocal
,因为 Session 之前当前会话周期内有效,会话结束便销毁。
内存泄漏问题
ThreadLocal
的不正确使用会导致内存泄漏。实际上 ThreadLocalMap
中使用的 key 为 ThreadLocal
的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。JVM 利用调用 remove、get、set 方法的时候,会清除线程 ThreadLocalMap
里所有 key 为 null 的 value,回收弱引用。
所以如果 ThreadLocal
没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap
中使用这个 ThreadLocal
的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。
当使用静态 ThreadLocal
的时候,延长 ThreadLocal
的生命周期,那也可能导致内存泄漏。因为,静态变量在类未加载的时候,它就已经加载,当线程结束的时候,静态变量不一定会回收。
ThreadLocal
出现内存泄漏条件:
ThreadLocal
引用被设置为 null,且后面没有 set、get、remove 操作。- 线程一直运行,不停止。(线程池)
- 触发了垃圾回收。(Minor GC或Full GC)
如何避免内存泄漏:
ThreadLocal
声明为private final
。private
与final
尽可能不让他人修改变更引用,最好不要声明为静态的。ThreadLocal
使用后务必调用remove
方法。最简单有效的方法是使用后将其移除。
总结
ThreadLocalMap
并不是为了解决线程安全问题,而是提供了一种将实例绑定到当前线程的机制,类似于隔离的效果。每个线程维护一个 ThreadLocalMap
的映射表,映射表的 key 是 ThreadLocal
实例本身,value 是要存储的副本变量。ThreadLocal
实例本身并不存储值,它只是提供一个在当前线程中找到副本值的 key。
ThreadLocal
设计的初衷是为了解决多线程编程中的资源共享问题。对比 synchronized
,synchronized
采取的是“以时间换空间”的策略,本质上是对关键资源上锁,让大家排队操作。而 ThreadLocal
采取的是“以空间换时间”的思路,为每个使用该变量的线程提供独立的变量副本,在本线程内部,它相当于一个“全局变量”,可以保证本线程任何时间操纵的都是同一个对象。
ThreadLocal
类最重要的一个概念是,其原理是通过一个 ThreadLocal
的静态内部类 ThreadLocalMap
实现,但是实际中,ThreadLocal
不保存 ThreadLocalMap
,而是有每个 Thread 内部维护 ThreadLocal.ThreadLocalMap threadLocals
一份数据结构。