前言
ThreadLocal 如果直接翻译的话,可以翻译为线程本地(副本),即每一个线程都有自己的数据副本,那么,在多线程情况下,这个数据在线程之间就是相互隔离的,不用担心数据被破坏。
场景
假设有一个方法调用链,这条方法调用链上都需要使用到同一个变量来实现功能,例如:某一个业务场景中,我们需要调用多个方法,每个方法都需要使用到登录用户的信息进行判断、鉴权等操作,很容易想到这种调用情况。
public void function(User user) {
......;
f1(user);
......;
f2(user);
......;
f3(user);
......
}
这种情况下,每一个方法都需要去传递一个需要共同使用的变量,这个时候可以把它抽取为一个静态变量,各个方法直接在方法内部获取这个变量即可。
public static User user;
public void function() {
......;
f1();
......;
f2();
......;
f3();
......
}
这样子,在单线程情况下可以很好地满足需求,但是在多个线程下,各个线程都会操作这个变量,很容易造成 A 线程修改了 user 的部分信息,B 线程读取了 user 被修改的信息。或者试想以下,这个变量是一个数据库连接实例,A 线程刚获取到这个实例,B 线程就将这个连接关闭、销毁了,那么 A 线程就无法继续进行正常业务了。
这时,引入 ThreadLocal 类保存这些各个线程要操作的变量,就不会导致各个线程操作同一个数据了,因为各个线程都有自己的数据副本。
使用
public class Main {
private static ThreadLocal<Integer> localData = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
System.out.println("线程 1 获取数据:" + localData.get());
localData.set(1);
for (int i = 0; i < 5; i++) {
localData.set(localData.get()+1);
System.out.println("线程 1 获取数据:" + localData.get());
}
}).start();
new Thread(() -> {
System.out.println("线程 2 获取数据:" + localData.get());
localData.set(2);
for (int i = 0; i < 5; i++) {
localData.set(localData.get()*2);
System.out.println("线程 2 获取数据:" + localData.get());
}
}).start();
}
}
运行结果:
线程 1 获取数据:null
线程 2 获取数据:null
线程 2 获取数据:4
线程 1 获取数据:2
线程 2 获取数据:8
线程 1 获取数据:3
线程 2 获取数据:16
线程 1 获取数据:4
线程 2 获取数据:32
线程 1 获取数据:5
线程 2 获取数据:64
线程 1 获取数据:6
可以看到线程 1 和线程 2 的数据操作都互不影响。
原理分析
ThreadLocal 的 set 方法
首先看 LocalData 的 set() 方法
public class ThreadLocal<T> {
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 调用 LocalData 的 getMap 方法,参数是当前线程
// 得到 一个 ThreadLocalMap 对象,存放传递过来的值,key 为 ThreadLocal 实例
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
// 返回当前线程的 threadLocals 变量
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
}
public class Thread implements Runnable {
......;
// ThreadLocalMap 是 Thread 的内部静态类,本质上就是一个 Entry,通过 key-value 存储
ThreadLocal.ThreadLocalMap threadLocals = null;
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
......;
}
简单来说,Thread 中有一个 ThreadLocalMap 内部类,其本质上就是一个 Entry,通过 key-value 存储值,其中 key 为每一个 ThreadLocal 对象实例,value 为每一个线程想要保存的值。
当线程通过 ThreadLocal 变量调用 set 方法时,本质上是获取当前线程的实例中的一个 Entry,然后以 ThreadLocal 的实例为 key 保存值,如果有多个 ThreadLocal 对象,就保存多个 key-value。这样,每个线程中的数据就相互隔离了,不会被其他线程破坏。
ThreadLocal 的 get 方法
再看看 get 方法
public class ThreadLocal<T> {
public T get() {
// 获取当前线程,获取线程中的 ThreadLocalMap 对象
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// key 为 ThreadLocal 对象,获取这个 key 对应的值,并返回
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果事先没有调用过 set 方法,那么会调用这个方法,默认返回 null
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
// .............
return value;
}
protected T initialValue() {
return null;
}
}
经过上面 set 方法的源码分析,也可以看出 get 方法本质上也是通过操作当前ThreadLocal实例的当前线程的 ThreadLocalMap 实例获取线程保存的值的。
小结
当一个线程需要反复使用同一个对象实例时,我们可以将其抽离为静态成员变量,但为了线程安全,我们可以使用 ThreadLocal 来保存这个静态成员变量,将其与其他线程隔离,不用担心被其他线程破坏。
其实,ThreadLocal 的操作,最后还是回归到了 Thread线程本身中,数据也是保存到了 Thread 线程实例中,ThreadLocal 只是起到了一个中间桥梁的作用。