InheritableThreadLocal和ThreadLocal的区别和使用场景

快人快语,先说结论,InheritableThreadLocal 是 ThreadLocal 的一个子类,它包含ThreadLocal 的所有功能并且扩展了 ThreadLocal 的功能,允许父线程中的 InheritableThreadLocal 变量的值被子线程继承。这意味着,当创建一个新的线程时,这个新线程可以访问其父线程中 InheritableThreadLocal 变量的值。

先说ThreadLocal

ThreadLocal 是 Java 中的一个类,它提供了一种线程局部(thread-local)变量。这些变量与普通的变量不同,因为每一个访问变量的线程都有其自己的独立初始化的变量副本。ThreadLocal 变量通常被私有 static 字段包含,它们被声明为 ThreadLocal 类型。

以下是 ThreadLocal 的主要特点和作用:

  1. 线程隔离ThreadLocal 为每个线程提供了它自己的变量副本。因此,一个线程不能访问或修改另一个线程的 ThreadLocal 变量。这有助于解决多线程环境下的数据共享和同步问题。
  2. 减少线程间同步:由于每个线程都有自己的 ThreadLocal 变量副本,因此不需要使用同步机制(如 synchronized)来避免并发访问问题。这可以提高多线程程序的性能。
  3. 方便管理线程上下文信息ThreadLocal 可以用来存储和访问线程特定的上下文信息,如用户ID、事务ID等。这样,在程序的不同部分,都可以方便地访问这些线程特定的信息。
  4. 内存泄漏问题:需要注意的是,ThreadLocal 可能会导致内存泄漏。当一个线程结束时,它使用的所有 ThreadLocal 实例通常不会被垃圾收集器回收,除非显式地删除这些实例。因此,在使用 ThreadLocal 时,需要确保在线程结束时正确地清理相关资源。

以下是一个简单的 ThreadLocal 示例:

public class ThreadLocalExample {
    // 创建一个 ThreadLocal 变量
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        // 线程1
        Thread thread1 = new Thread(() -> {
            threadLocal.set("Thread 1 data"); // 设置线程1的 ThreadLocal 变量
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 获取并打印线程1的 ThreadLocal 变量
        });

        // 线程2
        Thread thread2 = new Thread(() -> {
            threadLocal.set("Thread 2 data"); // 设置线程2的 ThreadLocal 变量
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 获取并打印线程2的 ThreadLocal 变量
        });

        thread1.start();
        thread2.start();
    }
}

在这个示例中,我们创建了一个 ThreadLocal<String> 变量,并在两个线程中分别设置了不同的值。每个线程都只能访问和修改它自己的 ThreadLocal 变量副本,因此输出将会是:

Thread-0: Thread 1 data
Thread-1: Thread 2 data

这显示了 ThreadLocal 如何为每个线程提供其自己的变量副本。
注意,上文案例是一个错误的用法,theadLocal变量一定要记得用后即焚,否则会有内存泄漏风险。当线程不再需要时,应该显式地调用ThreadLocal实例的remove()方法,以从当前线程的ThreadLocalMap中移除对应的条目。这样可以确保即使线程对象被回收,其关联的ThreadLocal变量也不会继续占用内存。

我很想从源码的角度讲一讲ThreadLocal的故事,比如ThreadLocal是怎么做到线程隔离的,ThreadLocal和ThreadLocalMap的关系等等,这些东西其实翻一翻ThreadLocal的源码就很快有答案,建议从它的set方法开始看起。本文不多做介绍。

InheritableThreadLocal

简单来说,InheritableThreadLocal就是可以继承给子线程的ThreadLocal,ThreadLocal本身并不能继承给子线程,示例如下:

    public static void main(String[] args) {
        //
        // 线程1
        Thread thread1 = new Thread(() -> {
            threadLocal.set("Thread 1 data"); // 设置线程1的 ThreadLocal 变量
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 获取并打印线程1的 ThreadLocal 变量
            
            // 创建子线程  
            Thread childThread = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 子线程尝试获取并打印父线程设置的ThreadLocal 变量的值  
            });
            childThread.setName("Child Thread");
            childThread.start();
        });

        // 线程2
        Thread thread2 = new Thread(() -> {
            threadLocal.set("Thread 2 data"); // 设置线程2的 ThreadLocal 变量
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 获取并打印线程2的 ThreadLocal 变量
        });

        thread1.start();
        thread2.start();
    }

运行结果:

Thread-0: Thread 1 data
Thread-1: Thread 2 data
Child Thread: null

这个案例说明ThreadLocal并不能继承给子线程,那么换成InheritableThreadLocal会如何:

    private static final ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
    public static void main(String[] args) {
        // 线程1
        Thread thread1 = new Thread(() -> {
            threadLocal.set("Thread 1 data"); // 设置线程1的 ThreadLocal 变量
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 获取并打印线程1的 ThreadLocal 变量
            // 创建子线程
            Thread childThread = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 子线程获取并打印父线程设置的 InheritableThreadLocal 变量的值
            });
            childThread.setName("Child Thread");
            childThread.start();
        });

        // 线程2
        Thread thread2 = new Thread(() -> {
            threadLocal.set("Thread 2 data"); // 设置线程2的 ThreadLocal 变量
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 获取并打印线程2的 ThreadLocal 变量
        });

        thread1.start();
        thread2.start();
    }

上面的代码案例仅仅改了一处,就是将threadLocal的对象类型改为了InheritableThreadLocal,其余不变,运行结果如下:

Thread-1: Thread 2 data
Thread-0: Thread 1 data
Child Thread: Thread 1 data

运行结果表示Child Thread成功拿到了父线程的ThreadLocal变量值,这是怎么做到ThreadLocal传递的?翻开Thread类的构造方法我们一目了然:

 private Thread(ThreadGroup g, Runnable target, String name,
                   long stackSize, AccessControlContext acc,
                   boolean inheritThreadLocals) {
        // 获取父线程变量
        Thread parent = currentThread();
		// 如果父线程的inheritableThreadLocals有值,则传递给当前线程
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    }

引申思考

本文的重点来了,如上面源码所示,InheritableThreadLocal是在Thread类的构造方法里面完成值传递的,如果是主线程不是使用new Thread()的方式而是使用了线程池,这时InheritableThreadLocal能够继承给子线程吗?

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>