解决多线程场景下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.

相关推荐
皮皮林5519 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
卡尔特斯13 小时前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
白鲸开源13 小时前
Ubuntu 22 下 DolphinScheduler 3.x 伪集群部署实录
java·ubuntu·开源
ytadpole13 小时前
Java 25 新特性 更简洁、更高效、更现代
java·后端
纪莫13 小时前
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
java·java面试⑧股
JavaGuide14 小时前
JDK 25(长期支持版) 发布,新特性解读!
java·后端
用户37215742613514 小时前
Java 轻松批量替换 Word 文档文字内容
java
白鲸开源14 小时前
教你数分钟内创建并运行一个 DolphinScheduler Workflow!
java
CoovallyAIHub15 小时前
中科大DSAI Lab团队多篇论文入选ICCV 2025,推动三维视觉与泛化感知技术突破
深度学习·算法·计算机视觉
Java中文社群15 小时前
有点意思!Java8后最有用新特性排行榜!
java·后端·面试