Java InheritableThreadLocal 继承自 ThreadLocal,是 Java ThreadLocal 的升级版。
这个升级体现在,子线程会继承父线程的 InheritableThreadLocal 值。
建议先阅读: Java ThreadLocal 。
基本使用
public class InheritableThreadLocalTest {
private static InheritableThreadLocal<Integer> logId = new InheritableThreadLocal<>();
public static void showLogId() {
System.out.printf("%s : %s\n", Thread.currentThread().getName(), logId.get());
}
public static void main(String[] args) {
showLogId();
logId.set(10);
showLogId();
logId.set(20);
showLogId();
}
}
运行结果:
main : null
main : 10
main : 20
在多线程中使用
下面的 logId、showLogId 来自上面的示例。
showLogId();
logId.set(10);
showLogId();
Thread t1 = new Thread(()->{
showLogId();
logId.set(20);
showLogId();
});
Thread t2 = new Thread(()->{
showLogId();
logId.set(30);
showLogId();
});
t1.start(); // 运行线程 t1
t2.start(); // 运行线程 t2
t1.join(); // 等待线程 t1 执行完
t2.join(); // 等待线程 t2 执行完
showLogId(); // 再看下当前线程的 logId 值
运行结果:
main : null
main : 10
Thread-0 : 10
Thread-0 : 20
Thread-1 : 10
Thread-1 : 30
main : 10
可以看到,main线程在将logId设置为10之后,新建两个线程,在这两个线程(Thread-0 、Thread-1)中 logId 的初始值都是10。
在线程池中使用依然可能遇到问题
如果线程池中线程会被复用,这时 InheritableThreadLocal 的值也会被复用。
为什么说依然,因为 ThreadLocal 也会遇到这种情况。见 Java ThreadLocal。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class InheritableThreadLocalTest {
private static InheritableThreadLocal<Integer> logId = new InheritableThreadLocal<>();
public static void showLogId() {
System.out.printf("%s : %s\n", Thread.currentThread().getName(), logId.get());
}
public static void main(String[] args) throws InterruptedException {
showLogId();
logId.set(10);
showLogId();
// 只有一个线程的线程池,该线程会复用
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 提交task
executorService.submit(() -> {
showLogId();
logId.set(20);
showLogId();
});
// 再提交一个task
executorService.submit(() -> {
showLogId();
logId.set(30);
showLogId();
});
// 关闭线程池
executorService.shutdown();
// 等待至任务都执行完,最多等3秒
executorService.awaitTermination(3, TimeUnit.SECONDS);
showLogId();
}
}
运行结果:
main : null
main : 10
pool-1-thread-1 : 10
pool-1-thread-1 : 20
pool-1-thread-1 : 20
pool-1-thread-1 : 30
main : 10
线程池只有一个线程处理任务,任务1执行后,logId从10变成20;任务2开始执行时,logId 不是 10,而是20。
不要对内部的值进行修改
public class InheritableThreadLocalTest {
private static class LogId {
private Integer value;
public LogId(Integer logId) {
this.value = logId;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
private static InheritableThreadLocal<LogId> logId = new InheritableThreadLocal<>();
public static void showLogId() {
System.out.printf("%s : %s\n", Thread.currentThread().getName(), logId.get().getValue());
}
public static void main(String[] args) throws InterruptedException {
logId.set(new LogId(0)); // 设置LogId,value为0
logId.get().setValue(10); // 修改了LogId对象的内部值
showLogId();
Thread t1 = new Thread(()->{
showLogId();
logId.get().setValue(20); // 修改了LogId对象的内部值
showLogId();
});
t1.start(); // 运行线程 t1
t1.join(); // 等待线程 t1 执行完
showLogId(); // 再看下当前线程的 logId value 值
}
}
运行结果:
main : 10
Thread-0 : 10
Thread-0 : 20
main : 20
上面的示例中,由于两个线程中 InheritableThreadLocal 变量 logId 中的 LogId 对象是同一个,所以子线程修改值为 20 之后, main 线程输出 logId 的value 也变成了 20 。 这个是有问题的,不要用logId.get().setValue(10);
,而应该用logId.get(new LogId(10));
。
InheritableThreadLocal 原理
Java ThreadLocal 介绍了 ThreadLocal 的原理。因为 InheritableThreadLocal 继承自 ThreadLocal,所以原理差不多。
不同点:
- 每个Thread对象都有一个 ThreadLocalMap 类型的实例变量 threadLocals,也要一个同类型的实例变量 inheritableThreadLocals。InheritableThreadLocal 用 inheritableThreadLocals 存数据。
- 在线程1中新建线程2时,因为线程2的创建是在线程1中,所以线程2可以拿到线程1的 inheritableThreadLocals ,然后复制一份给自己。于是,线程2启动后,便继承了线程1 inheritableThreadLocals 。