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

相关推荐
艾伦~耶格尔30 分钟前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构
man201734 分钟前
基于spring boot的篮球论坛系统
java·spring boot·后端
阿史大杯茶44 分钟前
Codeforces Round 976 (Div. 2 ABCDE题)视频讲解
数据结构·c++·算法
2401_858120531 小时前
Spring Boot框架下的大学生就业招聘平台
java·开发语言
S hh1 小时前
【Linux】进程地址空间
java·linux·运维·服务器·学习
LluckyYH1 小时前
代码随想录Day 58|拓扑排序、dijkstra算法精讲,题目:软件构建、参加科学大会
算法·深度优先·动态规划·软件构建·图论·dfs
转调1 小时前
每日一练:地下城游戏
开发语言·c++·算法·leetcode
Java探秘者1 小时前
Maven下载、安装与环境配置详解:从零开始搭建高效Java开发环境
java·开发语言·数据库·spring boot·spring cloud·maven·idea
攸攸太上1 小时前
Spring Gateway学习
java·后端·学习·spring·微服务·gateway
2301_786964361 小时前
3、练习常用的HBase Shell命令+HBase 常用的Java API 及应用实例
java·大数据·数据库·分布式·hbase