共计 5786 个字符,预计需要花费 15 分钟才能阅读完成。
根本应用
JDK 的 lang 包下提供了 ThreadLocal 类,咱们能够应用它创立一个线程变量,线程变量的作用域仅在于此线程内。
用 2 个示例来展现一下 ThreadLocal 的用法。
示例一:
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
System.out.println(threadLocal.get());
threadLocal.set(1);
System.out.println(threadLocal.get());
threadLocal.remove();
System.out.println(threadLocal.get());
输入:
null
1
null
这个示例展现了 ThreadLocal 提供的所有办法,ThreadLocal 中提供了三个办法,别离是:
- get:获取变量值
- set:设置变量值
- remove:删除变量值
示例二:
// 创立一个 MyRun 类
class MyRun implements Runnable {
// 创立 2 个线程变量,var1、var2
private ThreadLocal<Integer> var1 = new ThreadLocal<>();
private ThreadLocal<String> var2 = new ThreadLocal<>();
@Override
public void run() {
// 循环调用 m 办法 5 次
for (int i = 0; i < 5; i++) {m();
}
}
public void m() {
// 以后线程名称
String name = Thread.currentThread().getName();
// var1 变量从 1 开始,m 每次调用递增 1
Integer v = var1.get();
if(v == null) {var1.set(1);
}else {var1.set(v + 1);
}
// var2 变量 = 线程名 - var1 值
var2.set(name + "-" + var1.get());
// 打印
print();}
public void print() {String name = Thread.currentThread().getName();
System.out.println(name + ", var1:" + var1.get() + ", var2:" + var2.get());
}
}
创立 2 个线程,执行同一个 MyRun:
MyRun myRun = new MyRun();
Thread t1 = new Thread(myRun);
Thread t2 = new Thread(myRun);
t1.start();
t2.start();
输入:
Thread-0, var1: 1, var2: Thread-0-1
Thread-1, var1: 1, var2: Thread-1-1
Thread-0, var1: 2, var2: Thread-0-2
Thread-1, var1: 2, var2: Thread-1-2
Thread-0, var1: 3, var2: Thread-0-3
Thread-1, var1: 3, var2: Thread-1-3
Thread-0, var1: 4, var2: Thread-0-4
Thread-0, var1: 5, var2: Thread-0-5
Thread-1, var1: 4, var2: Thread-1-4
Thread-1, var1: 5, var2: Thread-1-5
示例二展现了 ThreadLocal 的重要特点:
两个线程执行的是同一个 MyRun 对象,如果 var1、var2 是一般的成员变量,两个线程拜访的将是同一个变量,这将会产生线程平安问题,然而从输入日志看来,t1、t2 的 var1、var2 值其实是独立的,互不影响的。
这是因为 var1、var2 是 ThreadLocal 类型,即是线程变量,它是绑定在线程上的,哪个线程来拜访这段代码,就从哪个线程上获取 var1、var2 变量值,线程与线程之间是互相隔离的,因而也不存在线程平安问题。
原理解析
ThreadLocal 是如何实现这个成果的呢?
咱们能够从 ThreadLocal 的源代码中一探到底。
其中,最要害是 get 办法,我将 get 相干的源代码都提取进去如下:
public T get() {
// 获取以后线程对象
Thread t = Thread.currentThread();
// 从以后线程中获取 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// 从 ThreadLocalMap 对象中获取以后 ThreadLocal 对应 Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
// 若 Entry 不为 null,返回值
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果获取 ThreadLocalMap 对象为 null 则返回默认值
return setInitialValue();}
// 从指定线程对象获取 ThreadLocalMap,也就是 Thread 中的 threadLocals
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}
// 默认值
private T setInitialValue() {T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);// 如果以后线程的 threadLocals 不为 null,则赋默认值
else
createMap(t, value); // 如果以后线程的 threadLocals 为 null,则新建
return value;
}
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}
protected T initialValue() {return null; // 初始值是 null}
从以上这段代码能够看出,ThreadLocal 拜访的实际上是以后线程的成员变量 threadLocals。
threadLocals 的数据类型是 ThreadLocalMap,这是 JDK 中专门为 ThreadLocal 设计的数据结构,它实质就是一个键值对类型。
ThreadLocalMap 的键存储的是以后 ThreadLocal 对象,值是 ThreadLocal 对象理论存储的值。
当用 ThreadLocal 对象 get 办法时,它实际上是从以后线程的 threadLocals 获取键为以后 ThreadLocal 对象所对应的值。
画张图来辅助一下了解:
分明了 ThreadLocal 的 get 原理,set 和 remove 办法不须要看源码也能猜出是怎么写的。
无非是以 ThreadLocal 对象为键设置其值或删除键值对。
ThreadLocal 的初始值
下面的介绍,咱们看到 ThreadLocal 的 initialValue 办法永远都是返回 null 的:
protected T initialValue() {return null; // 初始值是 null}
如果想要设定 ThreadLocal 对象的初始值,能够用以下办法:
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->1);
System.out.println(threadLocal.get());
withInitial 办法内理论返回的是一个 ThreadLocal 子类 SuppliedThreadLocal 对象。
SuppliedThreadLocal 重写了 ThreadLocal 的 initialValue 办法。
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {return supplier.get();
}
}
获取父线程的 ThreadLocal 变量
在一些场景下,咱们可能须要子线程能获取到父线程的 ThreadLocal 变量,但应用 ThreadLocal 是无奈获取到的:
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {threadLocal.set(1);
System.out.println(threadLocal.get());
Thread childThread = new Thread(() -> System.out.println(threadLocal.get()));
childThread.start();}
输入:
1
null
应用 ThreadLocal 的子类 InheritableThreadLocal 能够达到这个成果:
public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {threadLocal.set(1);
System.out.println(threadLocal.get());
Thread childThread = new Thread(() -> System.out.println(threadLocal.get()));
childThread.start();}
1
1
InheritableThreadLocal 是怎么做到的呢?
咱们来剖析一下 InheritableThreadLocal 的源代码。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {protected T childValue(T parentValue) {return parentValue;}
ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}
void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
InheritableThreadLocal 的源代码并不多,次要是 笼罩了 ThreadLocal 的三个办法 childValue、getMap、createMap。
childValue 办法用于 ThreadLocalMap 外部应用,咱们不打算解说 ThreadLocalMap 外部设计,这里能够疏忽;
ThreadLocal 原本 getMap、createMap 读写的是以后 Thread 对象的 threadLocals 变量。
而 InheritableThreadLocal 将其改为了读写以后 Thread 对象的 InheritableThreadLocal 变量。
接着咱们要从 Thread 类的源码查找脉络。
Thread 类源代码中,咱们能够看到有这么 2 个成员变量:
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
如果是应用 ThreadLocal 创立线程变量,读写的是 Thread 对象的 threadLocals;
如果是应用 InheritableThreadLocal 创立线程变量,读写的是 Thread 对象的 inheritableThreadLocals。
在 Thread 类的 init 办法能够看到(Thread 所有构造方法都是调用 init 办法,这边仅贴出要害局部):
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
ThreadLocal.createInheritedMap:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);
}
如果父级线程的 inheritableThreadLocals 不为 null,那么将父级线程的 inheritableThreadLocals 赋值到以后线程的 inheritableThreadLocals 变量。
总结:当应用 InheritableThreadLocal 创立线程变量时,父线程读写线程变量理论是写入父线程的 inheritableThreadLocals 中,在创立子线程时,会将父线程的 inheritableThreadLocals 复制给子线程的 inheritableThreadLocals,子线程操作此线程变量时,也是读写本人线程的 inheritableThreadLocals,这就达到了子线程能够获取父线程 ThreadLocal 的成果。
其余要点
- 如果应用了线程池,线程是会被复用的,因而线程的 threadLocals 和 inheritableThreadLocals 也会复用,在线程池应用 ThreadLocal 可能会产生一些问题,须要注意;
- JDK 自身提供创立线程池的办法,是不反对取得父级线程的 ThreadLocal 变量的。