需求场景
需要多线程运行程序时候,虽然有很多变量可以是不同线程间通用,但是还是有些变量希望做到线程隔离,每个线程需要自己独有的一些变量的场景模型
ThreadLocal 介绍
早在 jdk 1.2 的版本中就提供了 java.lang.ThreadLocal 的支持,这个工具可以帮助我们很方便的写出多线程的一些程序
常用方法如下:
- ThreadLocal.get 获取当前线程存储进 ThreadLocal 的变量
- ThreadLocal.set 设置当前线程 ThreadLocal 中的变量
- ThreadLocal.remove 移除当前线程 ThreadLocal 中的变量
ThreadLocal 一些问题
- 同一个线程,多次 new ThreadLocal,对不同的对象 set(不同参数) 操作之后,当前线程本地存储的值会被覆盖吗?
答案是不会的,这些值会全部存进线程相关的某个 map 中,这些值也依然做到线程隔离,但是不会被覆盖,都存在了本地线程存储中 - 其底层原理是通过存储线程号加上值的 map 形式吗?
答案是否定的,是通过存储 ThreadLocal 对象(key)加上值(value)的形式存储进一个线程相关的 map 中
这些问题都可以查看下方底层原理,从中可以得到解释
ThreadLocal 底层原理
当我们在一个类中设立一个 static ThreadLocal,在不同线程中存取 ThreadLocal 时候为什么是不同的值呢?
因为其底层代码如下:
这是 ThreadLocal.set 方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
这是 ThreadLocal.get 方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
我们可以发现,当我们写 ThreadLocal 变量虽然是 static 静态,但是不同线程存储时候却能够做到线程之间不串扰,这是因为,当我们每次 set 存数据时候,虽然threadLocal.set("aaa")
只用填 value,但实际底层代码中会先拿到当前线程,然后依据当前线程拿到属于当前线程的一个 map 键值对表,这个 map 才是关键所在,然后将 this 也就是当前这个 ThreadLocal 对象作为 key,将 aaa 作为 value 存储进 map 中,因为这个 map 是做到线程隔离的,所以可以发现我们虽然把 ThreadLocal 弄成静态,并且通过threadLocal.set("aaa")
方式存储数据,但是多线程使用中也不会造成数据串扰
举例
A 类中大部分数据是多线程通用的,但是有一个数据是多线程区分的,跑多线程时候,每跑一个线程就会new A()
一次
public class A{
private String param1 = "a";
private static String param2 = "b";
private static String param3 = "c";
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
// 一些操作之后将某个变量保存进线程本地存储中
public void operate(String str){
String string = str + Math.random();
threadLocal.set(String);
}
// 提取变量
public String take(){
return threadLocal.get();
}
}
试想一下要是 ThreadLocal 不是 static,那么每次创建 A 的时候,产生都是新的 ThreadLocal,根据其底层原理,其底层是一个线程有关的 map,key 是 ThreadLocal 对象,因为 ThreadLocal 要是不弄成静态,也就是说每次存进 map 时候 key 都不同,这样就变成了存了好几个 key-value 到一个线程相关的 map 中,然后我取的时候还要找到指定的 ThreadLocal 对象再调用其 get 方法才能取,但同样功能上也做到了线程隔离
上述例子中,b 和 c 多线程中是公用的,一般我会遇到每起一个线程会 new 一个 A,这个 A 中的 a 就成了线程隔离的变量了,c 也是线程隔离的变量