1 ThreadLocal 介绍
1.1 基本介绍
ThreadLocal 类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过 get 和 set 方法访问 ) 时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal 实例通常来说都是 private static 类型的,用于关联线程和线程上下文。
我们可以得知 ThreadLocal 的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
总结:
- 线程并发:在多线程的场景下
- 传递数据:我们可以通过 ThreadLocal 在同一线程,不同组件中传递公共变量
- 线程隔离:每个线程的变量都是独立的,不会互相影响
1.2 基本使用
1.2.1 常用方法
方法声明
描述
ThreadLocal()
构造方法,用于创建对象
public void set(T value)
设置当前线程绑定的局部变量
public T get()
获取当前线程绑定的局部变量
public void remove()
删除当前线程绑定的局部变量(注意:该操作特别重要,防止内存溢出)
1.2.2 使用案例
package xyz.xiaolinz.demo1;
/**
*
* @author huangmuhong
* @version 1.0.0
* @date 2023/1/12
*
*
* ThreadLocal:
* 1. set(): 给当前变量绑定一个值
* 2. get(): 获取当前变量的值
**/
public class ThreadLocalDemo {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
final ThreadLocalDemo demo = new ThreadLocalDemo();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
demo.set(Thread.currentThread().getName() + "的数据");
System.out.println(Thread.currentThread().getName() + "====>" + demo.get());
// 使用完成时显示调用remove()方法
demo.remove();
}).start();
}
}
/**
* 绑定变量到当前线程
*
* @param value 值
*/
public void set(String value) {
threadLocal.set(value);
}
/**
* 获取当前线程绑定的变量
*
* @return 值
*/
public String get() {
return threadLocal.get();
}
/**
* 去除绑定变量
* 该方法不是必须的,因为ThreadLocalMap中的Entry是使用弱引用的,所以当线程结束时,会自动被回收,不会造成内存泄漏
* 但是当其他对象持有ThreadLocal的引用时,就需要手动调用该方法去除绑定变量,否则会造成内存泄漏
*/
public void remove() {
threadLocal.remove();
}
}
1.3 ThreadLocal 类与 synchronized 关键字
1.3.1 synchronized 同步方式
synchronized 简单案例
package xyz.xiaolinz.demo2.demo1;
/**
*
* @author huangmuhong
* @version 1.0.0
* @date 2023/1/12
*
*
* ThreadLocal:
* 1. set(): 给当前变量绑定一个值
* 2. get(): 获取当前变量的值
**/
public class SynchronizedDemo {
private String content;
public static void main(String[] args) {
final SynchronizedDemo demo = new SynchronizedDemo();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (demo) {
demo.setContent(Thread.currentThread().getName() + "的数据");
System.out.println(Thread.currentThread().getName() + "====>" + demo.getContent());
}
}).start();
}
}
/**
*
*
* @return 值
*/
public String getContent() {
return content;
}
/**
*
*
* @param value 值
*/
public void setContent(String value) {
content = value;
}
}
从结果可以发现,加锁的方案同样也可以实现线程隔离的效果,但是在代码中加锁会导致程序失去了并发性,程序只能阻塞的等待前一个线程将 demo 对象的锁释放后才能继续去操作 content 的值。所以给该方案降低了程序的性能
1.3.2 ThreadLocal 与 synchronized 的区别
虽然 ThreadLocal 模式与 synchronized 关键字都用于处理多线程并发访问变量的问题,不过两者处理的角度和思路不同
synchronized
ThreadLocal
原理
同步机制采用了“以时间换空见”的方式,只提供了一份变量,让不同的线程派对访问
ThreadLocal 采用“以空间换时间”的方式,为每个线程都提供了一份变量父本,从而实现同时访问而互不干扰
侧重点
多个线程间访问资源的同步 (阻塞)
多线程中让每个线程之间的数据相互隔离
总结:在刚刚的案例中,ThreadLocal 和 synchronized 都能解决问题,但是使用 ThreadLocal 是最佳选择,我们在开发中通长都会选择以“空间换时间”的方案将性能最大化,但是使用 ThreadLocal 需要注意内存泄漏的问题
2. ThreadLocal 的内部结构
.1 常见的误解
如外恩不去看源代码的话,可能会猜测 ThreadLocal 是这样子设计的:每个 ThreadLocal 都创建一个 Map,然后用线程作为 Map 的 key,要存储的局部比昂量作为 Map 的 value,这样就能达到各个线程的局部变量隔离的效果。这是最简单的设计方法,JDK 最早期的 ThreadLocal 确实是这样设计的,但现在早已不是了。
2.2 现在的设计
但是,JDK 后面优化了设计方案,在 JDK8 中 ThreadLocal
的设计是:每个 Thread
维护一个 ThreadLocalMap
,这个 Map 的 key
是 ThreadLocal
本身,value
擦汗 is 真正要存储的值 Object
。
具体程是这样的:
- 每个 Thread 线程内部都有一个 Map(ThreadLocalMap)
- Map 里面存储 ThreadLocal 对象(key)和线程的变量副本(value)
- Thread 内部的 Map 是由 ThreadLocal 维护的,由 ThreadLocal 负责向 map 获取和设置线程的变量值
- 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本隔离,互不干扰
2.3 JDK8 后设计的好处
- 每个 Map 存储的 Entry 数量变少
- 当 Thread 销毁时,ThreadLocalMap 也会随之销毁,减少内存的使用
3. ThreadLocal 的核心方法源码
基于 ThreadLocal 的内部结构,我们继续分析核心源码,深入了解原理;
方法:
方法声明
描述
protected T initalValue()
返回当前线程局部变量的初始值
public void set(T set)
设置当前线程绑定的局部变量
public T get()
获取当前线程绑定的局部变量
public void remove()
移除当前线程绑定的局部变量
3.1 set 方法
/**
* 设置当前线程对应的ThreadLocal的值
* @param value 将要保存在当前线程对应的ThreadLocal的值
*/
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取到线程维护的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 往map填充key为ThreadLocal,value为value副本变量的记录
// 可以看到ThreadLocal本身并不存储任何变量的副本值,其存在更像操作ThreadLocalMap的工具类
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
/**
* 获取当前线程Thread对象维护的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/*
*创建ThreadLocalMap,用于在当前线程(Thread)维护ThreadLocalMap为空的情况下使用
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
代码执行流程
- 首先获取当前线程,并根据当前线程获取一个 ThreadLocalMap
- 如果获取的 Map 不为空,则将参数设置到 Map 钟(当前的 ThreadLocal 为 key)
- 如果 Map 为空,则给该线程直接 new 一个 ThreadLocalMap,并将值记录
3.2 get 方法
/**
* 返回当前线程中保存着ThreadLocal的值
* 如果当前线程没有刺ThreadLocal变量
* 则它会通过调用{@link [[initialvalue]]}方法进行初始化值
*
*/
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 判断map存不存在
if (map != null) {
// 以当前ThreadLocal为key,调用getEntry获取对应的存储实体
ThreadLocalMap.Entry e = map.getEntry(this);
// 对结果实体进行判空
if (e != null) {
@SuppressWarnings("unchecked")
// 实际结果
T result = (T)e.value;
return result;
}
}
/*
初始化:有两种情况执行当前代码
第一种情况:map不存在,表示此线程没有维护的ThreadLocalMap对象
第二种情况:map存在,但是没有与当前ThreadLocal关联的Entry
*/
return setInitialValue();
}
/**
* 当读取不到对应值时使用当前方法初始化
*/
private T setInitialValue() {
// 调用initialValue方法获取初始值
// 此方法可以被子类重写,如果不重写返回null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
代码执行流程
- 首先获取当前线程,根据当前线程获取一个 Map
- 如果获取的 Map 不为空,则在 Map 中以 ThreadLocal 的引用作为 key 来在 Map 中获取对应的 Entry,否则到 4
- 如果 e 不为 null,则返回 e.value 否则转到 4
- Map 为空或者 e 为空,则通过 initialValue 函数获取初始值 Value,然后用 ThreadLocal 的引用和 value 作为 firstKey 和 firstValue 创建一个新 Map
评论区