解决多线程场景下ThreadLocal的变量传递问题

问题描述:

ThreadLocal可以用于存储线程独享的变量。可以方便的存储上下文信息,提升代码的简洁性。

然而,ThreadLocal的一个不足之处在于,它不支持在线程嵌套过程中自动地将数据从父线程传递到子线程。这意味着,即使主线程中设置了ThreadLocal变量,子线程默认情况下也无法访问这些变量。

一、引言

ThreadLocal的需求场景即TransmittableThreadLocal的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的执行组件情况下传递ThreadLocal值』则是TransmittableThreadLocal目标场景。

下面是几个典型场景例子。

  1. 分布式跟踪系统 或 全链路压测(即链路打标)
  2. 日志收集记录系统上下文
  3. SessionCache
  4. 应用容器或上层框架跨应用代码给下层SDK传递信息

详细需求参见文章末尾参考链接

二、线程内数据共享

2.1 ThreadLocal

ThreadLocal可以用于存储线程独享的变量。很容易想到可以使用ThreadLocal用于我们的多语言信息存储。

然而,ThreadLocal的一个不足之处在于,它不支持在线程嵌套过程中自动地将数据从父线程传递到子线程。这意味着,即使主线程中设置了ThreadLocal变量,子线程默认情况下也无法访问这些变量。

demo

public class TlTest {

    public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(1);

        ThreadLocal<String> threadLocal = new ThreadLocal<String>();
        threadLocal.set("main thread set value");

        executorService.submit(() -> {
            String value = threadLocal.get();
            System.out.printf("child threadA, value is : %s%n", value);
        });

        executorService.submit(() -> {
            String value = threadLocal.get();
            System.out.printf("child threadB, value is : %s%n", value);
        });
        
        Thread.sleep(1000);

        System.out.printf("main thread, value is : %s%n", threadLocal.get());

    }

}

运行结果:只有主线程中可以获取到数据,子线程无法获取到数据

2.2 InheritableThreadLocal

Java提供了InheritableThreadLocal,允许子线程继承并使用父线程中的线程本地变量。但是,当InheritableThreadLocal应用于线程池时,它可能会引发问题。线程池中的核心线程通常不会在每次任务完成后立即被回收,这意味着存储在InheritableThreadLocal中的数据也不会被清除。因此,当线程被复用后,执行后续任务时,新任务可能会读取到旧任务留下的脏数据。

demo:

public class ITlTest {

    public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(1);

        InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();
        threadLocal.set("main thread set value");

        executorService.submit(() -> {
            String value = threadLocal.get();
            System.out.printf("child threadA, lang is : %s%n", value);
            threadLocal.set("child threadA set Value");
        });

        executorService.submit(() -> {
            String value = threadLocal.get();
            System.out.printf("child threadB, lang is : %s%n", value);
            threadLocal.set("child threadB set Value");
        });

        Thread.sleep(1000);

        System.out.printf("main thread, value is : %s%n", threadLocal.get());

    }

}

运行结果:子线程B获得到了子线程A中设置的脏数据。

2.3 TransmittableThreadLocal

TransmittableThreadLocal(TTL)是对TransmittableThreadLocal的一个改进,它解决了线程池等池化场景中的数据传递问题。通过提供一种机制来传递ThreadLocal值,TTL确保了在异步执行和上下文传递数据时的准确性。这使得线程池中的线程能够在执行任务时正确地接收和传递数据,避免了因数据不一致性而可能导致的问题。

demo:

public class TtdlTest {

    public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(1);
        executorService = TtlExecutors.getTtlExecutorService(executorService);

        TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<String>();
        threadLocal.set("main thread set value");

        executorService.submit(() -> {
            String value = threadLocal.get();
            System.out.printf("child threadA, lang is : %s%n", value);
            threadLocal.set("child threadA set Value");
        });

        executorService.submit(() -> {
            String value = threadLocal.get();
            System.out.printf("child threadB, lang is : %s%n", value);
            threadLocal.set("child threadB set Value");
        });

        Thread.sleep(1000);

        System.out.printf("main thread, value is : %s%n", threadLocal.get());


    }

}

运行结果:主线程、子线程A、子线程B均可以正确的读取到主线程中设置的数据

三、结论

TransmittableThreadLocal提供了用于在多线程和异步执行场景中传递上下文数据的解决方案。通过使用TTDL,可以避免线程安全问题,减少线程池中的数据不一致问题,简化代码逻辑,提高应用程序的国际化和可维护性。

👉 TransmittableThreadLocal(TTL):在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。一个Java标准库本应为框架/中间件设施开发提供的标配能力,本库功能聚焦 & 0依赖,支持Java 6~21

JDKInheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时ThreadLocal值传递到 任务执行时

本库提供的TransmittableThreadLocal类继承并加强InheritableThreadLocal类,解决上述的问题,使用详见 User Guide

整个TransmittableThreadLocal库的核心功能(用户API、线程池ExecutorService/ForkJoinPool/TimerTask及其线程工厂的Wrapper;开发者API、框架/中间件的集成API),只有 ~1000 SLOC代码行,非常精小。

参考资料

•TransmittableThreadLocal官方GitHub仓库:GitHub - alibaba/transmittable-thread-local: 📌 a missing Java std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components.

相关推荐
测开小菜鸟24 分钟前
使用python向钉钉群聊发送消息
java·python·钉钉
好奇龙猫1 小时前
【学习AI-相关路程-mnist手写数字分类-win-硬件:windows-自我学习AI-实验步骤-全连接神经网络(BPnetwork)-操作流程(3) 】
人工智能·算法
P.H. Infinity1 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天1 小时前
java的threadlocal为何内存泄漏
java
sp_fyf_20242 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01
人工智能·深度学习·神经网络·算法·机器学习·语言模型·数据挖掘
caridle2 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^2 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋32 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花2 小时前
【JAVA基础】Java集合基础
java·开发语言·windows
香菜大丸2 小时前
链表的归并排序
数据结构·算法·链表