ThreadLocal
提供线程的局部变量。这样的变量与它正常的对应变量不同之处在于,每个线程访问(通过ThreadLocal
的 get 或 set 方法)都具有自己的,独立初始化的变量副本。
我们很容易写出如下的用法:
ThreadLocal<Integer> count = new ThreadLocal<>();
System.out.println("初始值" + count.get());
count.set(1);
for (int i = 0; i < 3; i++) {
// 糟糕的线程创建方式
new Thread(){
@Override
public void run() {
Integer countV = count.get();
countV++;
System.out.println(Thread.currentThread().getName() + "对应的cout值为"+count.get());
}
}.start();
}
System.out.println("结束值" + count.get());
// 抛出空指针异常
以上的代码运行结果会抛出 java.lang.NullPointerException, 原因在于使用 countV++
时,并没有获取到之前设置的1。原因是因为当前的代码也是运行在线程中的(可能是你的main方法),ThreadLocal
针对每个线程都会有一个副本,所以 count.set(1)
并不能对其它线程生效。简单修改代码为如下:
ThreadLocal<Integer> count = new ThreadLocal(){
@Override
protected Object initialValue() {
return 1;
}
};
System.out.println("初始值" + count.get());
for (int i = 0; i < 3; i++) {
// 糟糕的线程创建方式
new Thread(){
@Override
public void run() {
Integer countV = count.get();
countV++;
System.out.println(Thread.currentThread().getName() + "对应的cout值为"+count.get());
}
}.start();
}
System.out.println("结束值" + count.get());
这样就能保证每个线程在初次使用时都能获取初始值。
ThreadLocal
的实现原理是:在每个Thread
对象中具有一个threadLocals
成员,该成员的类型是 ThreadLocal.ThreadLocalMap
,初始值为 null ;当线程调用ThreadLocal
的 get 或 set 方法时,会为该成员赋值。
// Thread 中的 threadLocals
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
// ThreadLocal 中该成员的创建
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
当通过 ThreadLocal 的 get 方法调用 createMap 方法时(当前线程的 threadLocals 尚未赋值),会首先调用 ThreadLocal 的 initialValue 方法,也就是在上面修改了以后的代码使用的方法;方法的返回值会作为 createMap 的 firstValue 参数传入。
当通过 ThreadLocal 的 set 方法调用 createMap 方法时(当前线程的 threadLocals 尚未赋值), set 方法的参数就直接作为 createMap的参数了。
这里没有去细细的研究 ThreadLocal.ThreadLocalMap 这个类,但以 Map 结尾,无疑是一个映射关系了;值得注意的是映射的 key 是当前的 ThreadLocal 对应的哈希值与一个固定值作与运算的结果,也就是说当在一个线程中调用多个 ThreadLocal 时,多个 ThreadLocal 组成了这个 Map ,值即为 ThreadLocal 所代表的值。
哈希值来源于 ThreadLocal 内部维护的一个静态 AtomicInteger ,所以在无需担心 key 值重复的问题
ThreadLocal 实例化对象被各个线程使用,但其代表的值却存储在当前线程自己的成员,在我看来,对各个线程来说,ThreadLocal 相当于一个 key ,每次取值时,各线程通过这个 key 去 成员map 中取出值,所以也就不会有什么线程安全问题了。值得注意是 ThreadLocal 中需要用到当前线程的方法, Thread t = Thread.currentThread();
都放在了方法的第一句去执行;
在父子线程中使用时,如果需要当前线程的 ThreadLocal 设置的值对于子类任然适用的话,则需要使用 InheritableThreadLocal
,该类继承自 ThreadLocal,Thread 类中维护了另一个成员 inheritableThreadLocals
, 该成员类型任然是 ThreadLocal.ThreadLocalMap
, 不过只有是 InheritableThreadLocal
实例化的对象才会存储到这里。也就是说对于可继承和不可继承的 ThreadLocal ,Thread 是分开维护的。
至于子线程为何能够继承到父线程的 ThreadLocal ,是因为在线程的构造函数中,会进行检测:
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
使用例子:
ThreadLocal<Integer> count = new InheritableThreadLocal() {
@Override
protected Object initialValue() {
return 1;
}
};
System.out.println("初始值" + count.get());
count.set(2);
// 糟糕的线程创建方式
new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "对应的cout值为" + count.get());
count.set(3);
new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "对应的cout值为" + count.get());
}
}.start();
}
}.start();
System.out.println("结束值" + count.get());
// 运行结果
初始值1
结束值2
Thread-0对应的cout值为2
Thread-1对应的cout值为3
文章最初的代码(创建匿名类)就介绍了如何去继承这个类,重写初始化方法来达到线程取值时的初始化;在 Java 8 中则有更优雅的方法(我之所以认为它是Java 8新增的,是因为其使用了 Supplier):
ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 1);
这种方法实现的原理也很好理解:
// withInitial方法
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
// SuppliedThreadLocal 类
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();
}
}