一:ThreadLocal的核心机制
1:每个Thread线程内部都有一个Map。
2:Map里面存储线程本地对象(key)和线程的变量副本(value)
Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰
- get()方法用于获取当前线程的副本变量值。
- set()方法用于保存当前线程的副本变量值。
- initialValue()为当前线程初始副本变量值。
- remove()方法移除当前前程的副本变量值。
get()方法
1.获取当前线程的ThreadLocalMap对象threadLocals
2.从map中获取线程存储的K-V Entry节点。
3.从Entry节点获取存储的Value副本值返回。
4.map为空的话返回初始值null,即线程变量副本为null,在使用时需要注意判断NullPointerException。
set()方法
1.获取当前线程的成员变量map
2.map非空,则重新将ThreadLocal和新的value副本放入到map中。
3.map空,则对线程的成员变量ThreadLocalMap进行初始化创建,并将ThreadLocal和value副本放入map中
二:TheadLocal模式与同步机制的区别
1, 同步机制采用了“以时间换空间”的方式,提供一份变量,让不同的线程排队访问
而ThreadLocal采用了“以空间换时间”的方式,为每一个线程都提供一份变量的副本,从而实现同时访问而互不影响。
2,同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式。
ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样不需要对多个线程进行同步了。
3, 如果你需要进行多个线程之间进行通信,则使用同步机制。
如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal。
三:使用场景
1. 实现单个线程单例以及单个线程上下文信息存储,比如请求id,交易id等
2. 实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例
3. 存放一些线程用到的相关数据,避免在方法中来回传递参数
为什么ThreadLocalMap使用弱引用存储ThreadLocal?
那通常说的ThreadLocal内存泄漏是如何引起的呢?
我们注意到Entry对象中,虽然Key(ThreadLocal)是通过弱引用引入的,但是value即变量值本身是通过强引用引入。
这就导致,假如不作任何处理,由于ThreadLocalMap和线程的生命周期是一致的,当线程资源长期不释放,即使ThreadLocal本身由于弱引用机制已经回收掉了,但value还是驻留在线程的ThreadLocalMap的Entry中。即存在key为null,但value却有值的无效Entry。导致内存泄漏。
但实际上,ThreadLocal内部已经为我们做了一定的防止内存泄漏的工作
private int expungeStaleEntry(int staleSlot);
作用是擦除某个下标的Entry(置为null,可以回收),同时检测整个Entry[]表中对key为null的Entry一并擦除,重新调整索引。
该方法,在每次调用ThreadLocal的get、set、remove方法时都会执行,即ThreadLocal内部已经帮我们做了对key为null的Entry的清理工作。
但是该工作是有触发条件的,需要调用相应方法,假如我们使用完之后不做任何处理是不会触发的
(强制)在代码逻辑中使用完ThreadLocal,都要调用remove方法,及时清理,同时对异常情况也要在finally中清理。
ThreadLocal一般加static修饰
假如使用强引用,当ThreadLocal不再使用需要回收时,发现某个线程中ThreadLocalMap存在该ThreadLocal的强引用,无法回收,造成内存泄漏。
因此,使用弱引用可以防止长期存在的线程(通常使用了线程池)导致ThreadLocal无法回收造成内存泄漏。