Java 学习笔记

所有文章
📖 最新文章 transmittable-thread-local 库 JOOR 反射库 Lombok 库:为你减少样板代码 Slf4j 日志组件的使用 加速maven、gradle依赖下载
📖 Java 基础 安装 第一个程序 使用 UTF-8 编写代码 空值 null 正则表达式 线程 日期/时间 匿名类 枚举 ThreadLocal 线程本地变量 动态代理 jar 命令

Java InheritableThreadLocal


Java InheritableThreadLocal 继承自 ThreadLocal,是 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 。


( 本文完 )

文章目录