彻底理解 ThreadLocal
彻底理解 ThreadLocal
- 本篇文章主要从一下几个角度来分析
ThreadLocal
1、
ThreadLocal的基本用途
2、ThreadLocal的具体实现
3、ThreadLocal使用时的注意事项
ThreadLocal 的基本用途
1、线程隔离
ThreadLocal的主要价值在于线程隔离,ThreadLocal中的数据只属于当前线程,其本地值对别的线程是不可见的,在多线程环境下,可以防止自己的变量被其他线程篡改。另外,由于各个线程
之间的数据相互隔离,避免了同步加锁带来的性能损失,大大提升了并发性的性能。
- 使用场景 :数据库连接独享、
Session数据管理
2、跨函数传递数据
通常用于同一个线程内,跨类、跨方法传递数据时,如果不用
ThreadLocal,那么相互之间的数据传递势必要靠返回值和参数,这样无形之中增加了这些类或者方法之间的耦合度。由于ThreadLocal的特性,同一线程在某些地方进行设置,在随后的任意地方都可以获取到。线程执行过程中所执行到的函数都能读写ThreadLocal变量的线程本地值,从而可以方便地实现跨函数的数据传递。使用ThreadLocal保存函数之间需要传递的数据,在需要的地方直接获取,也能避免通过参数传递数据带来的高耦合。
- 使用场景:每个线程绑定一个
Session(用户会话)信息,这样一个线程所有调用到的代码都可以非常方便地访问这个本地会话,而不需要通过参数传递。
ThreadLocal 的具体实现
- 下面我们主要从以下三个属性来具体分析
![]()
1、
ThreadLocalMap
2、get()
3、set(T value)
ThreadLocalMap

- 1、从上图中我们可以看到
ThreadLocalMap类成员属性中有一个Entry的静态内部类,这个类就是我们 TheadLocalMap 用来存储数据的节点Node
2、同时Entry继承了WeakReference,并使用WeakReference对 key 进行了包装
- 知识 : 为什么
ThreadLocalMap的 key 节点要使用WeakReference进行包装
void fn() {
ThreadLocal<String> local = new ThreadLocal<>();
local.set("123");
local.get();
}
- 代码内存结构
![]()
1、当线程执行完fn()方法后,fn()的方法栈帧将被销毁,强引用local的值也就没有了,但此时线程的
ThreadLocalMap中对应的Entry的Key引用还指向了ThreadLocal实例。如果Entry的Key引用是强引用,就会导致Key引用指向的ThreadLocal实例及其Value值都不能被GC回收,这将造成严重的内存泄漏.
- 当 Key 为强引用时
![]()
get()
- 代码
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程持有的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 根据 key 获取 value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 类型转换
T result = (T)e.value;
return result;
}
}
// 数据为空进行初始化防止 NPE
return setInitialValue();
}
1、通过
ThreadLocal的 get 方法我们可以看出,子线程想获取到父线程的ThreadLocal的数据是获取不到的,因为在 get 时是获取的父现场的的 Thread。
2、我们可以通过InheritableThreadLocal解决上面的问题
set()
- 代码
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程持有的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 设置数据 key = 当前的 ThreadLocal , v = value
map.set(this, value);
} else {
// 创建 ThreadLocalMap 并设置值
createMap(t, value);
}
}
ThreadLocal 使用时的注意事项
- 注意事项
1、尽量使用private static final修饰
ThreadLocal实例。使用 private 与final修饰符主要是尽可能不让他人修改、变更ThreadLocal变量的引用,使用static修饰符主要为了确保ThreadLocal实例的全
局唯一。
private static final ThreadLocal<?> local = ThreadLocal.withInitial(创建一个初始化对象);
2、
ThreadLocal在使用完之后需要进行 remove(),否则在使用线程池使用ThreadLocal时会造成 B 线程使用了 A 线程的脏数据的情况
- 如果使用线程池,可以定制线程池的
afterExecute方法,调用 ThreadLocal 的 remove 方法
//线程本地变量,用于记录线程异步任务的开始执行时间
private static final ThreadLocal<Long> START_TIME= new ThreadLocal<>();
ExecutorService pool = new ThreadPoolExecutor(2,
4, 60,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(2)) {
//省略其他
//异步任务执行完成之后的钩子方法
@Override
protected void afterExecute(Runnable target, Throwable t)
{
//省略其他
//清空ThreadLocal实例的本地值
START_TIME.remove();
}
};




浙公网安备 33010602011771号