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

相关推荐
不是二师兄的八戒16 分钟前
本地 PHP 和 Java 开发环境 Docker 化与配置开机自启
java·docker·php
爱编程的小生28 分钟前
Easyexcel(2-文件读取)
java·excel
带多刺的玫瑰1 小时前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
爱敲代码的憨仔1 小时前
《线性代数的本质》
线性代数·算法·决策树
yigan_Eins1 小时前
【数论】莫比乌斯函数及其反演
c++·经验分享·算法
阿史大杯茶1 小时前
AtCoder Beginner Contest 381(ABCDEF 题)视频讲解
数据结构·c++·算法
计算机毕设指导61 小时前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
Gu Gu Study1 小时前
枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~
java·开发语言
Chris _data1 小时前
二叉树oj题解析
java·数据结构
牙牙7052 小时前
Centos7安装Jenkins脚本一键部署
java·servlet·jenkins