ThreadLocal
ThreadLocal 将一个变量隔离在某一个线程上,即该变量只在某个线程本身可见,其他线程无法访问,可以实现线程的安全性,而且可以避免线程同步带来的性能损失
如图所示,每个 Thread对象中都持有一个 ThreadLocalMap 成员变量,每个 ThreadLocalMap 有一个键值对 Entry[] table,可以认为是一个 map,ThreadLocal 就是这个键,我们需要存储的数据做为值
使用场景
每个线程需要一个独享的对象
通常是工具类,典型需要使用的有 SimpleDateFormat 和 Random
Java8 可以使用线程安全的 DateTimeFormatter 和 ThreadLocalRandom
1 | public class DateUtil { |
每个线程内需要保存全局变量
例如在拦截器中获取用户信息,可以直接让不同方法直接使用,避免参数传递的麻烦
1 | public class ThreadLocalHolder { |
带来的好处
- 达到线程安全
- 不需要加锁,提高执行效率
- 更高效的利用内存,节省开销
- 免去传参的繁琐
主要方法介绍
- initialValue:初始化,该方法会返回当前线程对应的“初始值”,这是一个延迟加载的方法,只有在调用 get() 的时候才会触发
当线程第一次使用 get() 访问变量时,将调用此方法,除非线程之前调用了 set(),在这种情况下,不会为线程调用 initialValue()
通常,每个线程最多调用一次此方法,但如果已经调用了 remove() 后,再调用 get() ,则可能会再次调用此方法
如果不重写此方法,这个方法会返回 null,一般使用匿名内部类的方法来重写 initialValue(),以便在后续使用中可以初始化副本对象 - set:为当前线程副本设置一个新值,set() 和 setInitialValue() 最后都是利用 map.set() 来设置值
- get:得到当前线程副本对应的值,如果是首次调用 get(),则会调用 initialize 来得到这个值
- remove:删除对应这个线程的值
内存泄露问题
1 | static class Entry extends WeakReference<ThreadLocal<?>> { |
value 的泄露
ThreadLocalMap 中的每一个 Entry 都是对 key 的弱引用(弱引用的特点是,如果这个对象只被弱引用关联,那么这个对象就可以被回收),同时每个 Entry 都包含了一个对 value 的强引用
正常情况下,当线程终止,保存在 ThreadLocal 里的 value 会被垃圾回收,因为没有任何强引用了
但是,如果线程不终止(比如线程需要保持很久),那么 key 对应的 value 就不能被回收,所以有以下的调用链:Thread -> ThreadLocalMap -> Entry(key 为 null)-> value
因为 value 和 Thread 之间还存在这个强引用链路,所以导致 value 无法回收,就可能会出现 OOM
为了解决这个问题,JDK 会在 set、remove、rehash 方法中扫描 key 为 null 的 Entry,并把对应的 value 设置为 null,这样 value 对象就会被回收
但是如果一个 TreadLocal 不被使用,那么实际上 set、remove、rehash 方法也不会被调用,如果同时线程又不停止,那么调用链就会一直存在,那么就导致了 value 的内存泄露
如何避免
调用 remove 方法,就会删除对应的 Entry 对象,可以避免内存泄露,所以使用完 ThreadLocal 之后,应该调用 remove 方法