关于阿里巴巴TransmittableThreadLocal使用解读

 更新时间:2025年02月14日 15:41:28   作者:林犀居士  
文章主要介绍了三种ThreadLocal的使用:ThreadLocal、InheritableThreadLocal和TransmittableThreadLocal,ThreadLocal和InheritableThreadLocal在单线程和部分情况下可以正常工作,但TransmittableThreadLocal在处理线程池时表现更佳

前言

ThreadLocal在上下文的数据传输上非常的方便和简洁。

工业实践中,比较常用的有三个,ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal,那么他们三个之间有什么区别呢?

常见的三种ThreadLocal比较

ThreadLocalInheritableThreadLocalTransmittableThreadLocal
来源jdkjdk阿里开源
单线程数据传输支持支持支持
new线程数据传输不支持支持支持
线程池数据传输不支持部分支持【简单场景】支持

针对线程池的数据传输,InheritableThreadLocal仅仅能在一些简单场景下做到,下面就用一个案例来说明

  • 先看下线程工厂的定义
package com.tml.mouseDemo.core.threadLocalDemo;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

public class SimpleThreadFactory implements ThreadFactory {

    private static AtomicInteger atomicInteger = new AtomicInteger(1);
    @Override
    public Thread newThread(Runnable r) {

        Thread t = new Thread(r);
        t.setName("tml-"+atomicInteger.getAndIncrement());
        return t;
    }
}
  • InheritableThreadLocal部分支持的案例
package com.tml.mouseDemo.core.threadLocalDemo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalDemo1 {

    private static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

    private static ExecutorService service = Executors.newFixedThreadPool(2, new SimpleThreadFactory());


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


        threadLocal.set("hello main");
        for (int i = 0; i < 2; i++) {
            service.execute(() -> {
                String s = threadLocal.get();
                ThreadUtils.printLog("get data " + s);

            });
        }

        //修改threadLocal中的值
        threadLocal.set("hello world");
        Thread.sleep(2000);
        for (int i = 0; i < 2; i++) {

            service.execute(() -> {
                String s = threadLocal.get();
                ThreadUtils.printLog("get data " + s);

            });
        }


        ThreadUtils.printLog("get data " + threadLocal.get());

        service.shutdown();

    }
}
  • 运行结果如下

2025-01-10 19:41:50 | INFO | tml-1 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello main
2025-01-10 19:41:50 | INFO | tml-2 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello main
2025-01-10 19:41:52 | INFO | tml-2 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello main
2025-01-10 19:41:52 | INFO | main | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello world
2025-01-10 19:41:52 | INFO | tml-1 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello main

从运行结果来看,前面的两次循环提交的任务,在子线程中确实是能正常的获取主线程设置的变量,即hello main,但是紧接着,我修改了主线程上绑定的变量为hello world,然后继续循环两次提交两个任务,这个时候子线程中获取的线程变量依然是hello main,这明显是与是期望不一致的。

从这个层面来讲,InheritableThreadLocal确实在线程池的层面支持不够友好,可以说仅支持部分简单场景。

根本原因就死线程池的池化机制,从上面的运行日志上也可以看出,提交了4个任务执行的线程依然是两个。线程池中的线程是复用的,InheritableThreadLocal是在创建子线程的时候,会将主线程上绑定的数据拷贝过来,如果我不创建新的线程,但是主线程上绑定的数据改变了呢?那我依然还是读取到之前拷贝的数据。

这个就是InheritableThreadLocal的短板。针对这个问题,阿里开源的TransmittableThreadLocal就能顺利丝滑的解决这个问题。

TransmittableThreadLocal实践

maven依赖

<dependency>
     <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
     <version>2.12.4</version>
</dependency>

装饰Runnable任务

  • 直接看代码
package com.tml.mouseDemo.core.threadLocalDemo;

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalDemo1 {

    private static ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();

    private static ExecutorService service = Executors.newFixedThreadPool(2, new SimpleThreadFactory());


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


        threadLocal.set("hello main");
        for (int i = 0; i < 2; i++) {
            service.execute(TtlRunnable.get(() -> {
                String s = threadLocal.get();
                ThreadUtils.printLog("get data " + s);

            }));
        }

        //修改threadLocal中的值
        threadLocal.set("hello world");
        Thread.sleep(2000);
        for (int i = 0; i < 2; i++) {

            service.execute(TtlRunnable.get(() -> {
                String s = threadLocal.get();
                ThreadUtils.printLog("get data " + s);

            }));
        }


        ThreadUtils.printLog("get data " + threadLocal.get());

        service.shutdown();

    }
}
  • 运行结果如下:

2025-01-10 19:57:03 | INFO | tml-2 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello main
2025-01-10 19:57:03 | INFO | tml-1 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello main
2025-01-10 19:57:05 | INFO | tml-1 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello world
2025-01-10 19:57:05 | INFO | main | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello world
2025-01-10 19:57:05 | INFO | tml-2 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello world

与第一个案例的差异点在于,使用了

public static TtlRunnable get(@Nullable Runnable runnable) {
    return get(runnable, false, false);
}

来增强了了Runnable任务,执行的结果也是符合预期。

装饰线程池

  • 直接看代码
package com.tml.mouseDemo.core.threadLocalDemo;

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
import com.alibaba.ttl.threadpool.TtlExecutors;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalDemo1 {

    private static ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();

    private static ExecutorService service = Executors.newFixedThreadPool(2, new SimpleThreadFactory());

    private static ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(service);



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



        threadLocal.set("hello main");
        for (int i = 0; i < 2; i++) {
            ttlExecutorService.execute(() -> {
                String s = threadLocal.get();
                ThreadUtils.printLog("get data " + s);

            });
        }

        //修改threadLocal中的值
        threadLocal.set("hello world");
        Thread.sleep(2000);
        for (int i = 0; i < 2; i++) {

            ttlExecutorService.execute(() -> {
                String s = threadLocal.get();
                ThreadUtils.printLog("get data " + s);

            });
        }


        ThreadUtils.printLog("get data " + threadLocal.get());

        service.shutdown();

    }
}
  • 运行结果如下

2025-01-10 20:05:05 | INFO | tml-2 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello main
2025-01-10 20:05:05 | INFO | tml-1 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello main
2025-01-10 20:05:07 | INFO | tml-2 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello world
2025-01-10 20:05:07 | INFO | main | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello world
2025-01-10 20:05:07 | INFO | tml-1 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello world

与上一个案例的差异点在于,这里没有包装Runnable任务,而是包装了线程池,使用了

public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) {
    if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced) {
        return executorService;
    }
    return new ExecutorServiceTtlWrapper(executorService, true);
}

包装了ExecutorService,执行结果也是符合预期

使用java Agent无侵入增强线程池

  • 直接看代码
package com.tml.mouseDemo.core.threadLocalDemo;

import com.alibaba.ttl.TransmittableThreadLocal;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalDemo1 {

    private static ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();

    private static ExecutorService service = Executors.newFixedThreadPool(2, new SimpleThreadFactory());

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



        threadLocal.set("hello main");
        for (int i = 0; i < 2; i++) {
            service.execute(() -> {
                String s = threadLocal.get();
                ThreadUtils.printLog("get data " + s);

            });
        }

        //修改threadLocal中的值
        threadLocal.set("hello world");
        Thread.sleep(2000);
        for (int i = 0; i < 2; i++) {

            service.execute(() -> {
                String s = threadLocal.get();
                ThreadUtils.printLog("get data " + s);

            });
        }


        ThreadUtils.printLog("get data " + threadLocal.get());

        service.shutdown();

    }
}

项目运行的时候,需要添加额外的jvm启动参数,如下

  • 运行结果如下,也是符合预期

2025-01-10 20:11:59 | INFO | tml-2 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello main
2025-01-10 20:11:59 | INFO | tml-1 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello main
2025-01-10 20:12:01 | INFO | tml-2 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello world
2025-01-10 20:12:01 | INFO | main | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello world
2025-01-10 20:12:01 | INFO | tml-1 | com.tml.mouseDemo.core.threadLocalDemo.ThreadUtils | get data hello world

总结

阿里巴巴的TransmittableThreadLocal是继承自InheritableThreadLocal,对他的功能进行了增强,增强的点也主要是在线程池的支持上。

通过上面的三个案例,可以看到TransmittableThreadLocal是非常灵活的,大家可以根据自己的需要,选择对应的方式来实现。

TransmittableThreadLocal的官网

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • jdk11 jdk17多版本共存切换方式

    jdk11 jdk17多版本共存切换方式

    这篇文章主要介绍了jdk11 jdk17多版本共存切换方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-02-02
  • Java日期处理工具类DateUtils详解

    Java日期处理工具类DateUtils详解

    这篇文章主要为大家详细介绍了Java日期处理工具类DateUtils的相关代码,包含日期和时间常用操作,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • Java+mysql实现学籍管理系统

    Java+mysql实现学籍管理系统

    这篇文章主要为大家详细介绍了Java+mysql实现学籍管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • javaSE中数组的概念与使用详细教程

    javaSE中数组的概念与使用详细教程

    这篇文章主要给大家介绍了关于javaSE中数组的概念与使用的相关资料,数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标,需要的朋友可以参考下
    2023-08-08
  • JavaScript的基本类型值-String类型

    JavaScript的基本类型值-String类型

    String类型用于表示由零或多个16位Unicode字符组成的字符序列,即字符串。在JavaScript中没有单个的字符型,都是字符串。这篇文章主要介绍了JavaScript的基本类型值String类型,需要的朋友可以参考下
    2017-02-02
  • Java连接 JDBC基础知识(操作数据库:增删改查)

    Java连接 JDBC基础知识(操作数据库:增删改查)

    这篇文章主要介绍了Java连接 JDBC基础知识,包括操作数据库之增删改查操作,需要的朋友可以参考下
    2021-04-04
  • 使用Java操作MySQL实现数据交互的方法

    使用Java操作MySQL实现数据交互的方法

    JDBC是Java中用于操作数据库的API,可以为多种关系数据库提供统一访问,它通过JDK自带的JDBC API和数据库驱动包进行操作,实现数据库的增删改查,本文给大家介绍使用Java操作MySQL实现数据交互的方法,感兴趣的朋友跟随小编一起看看吧
    2025-01-01
  • SpringBoot如何取消内置Tomcat启动并改用外接Tomcat

    SpringBoot如何取消内置Tomcat启动并改用外接Tomcat

    这篇文章主要介绍了SpringBoot如何取消内置Tomcat启动并改用外接Tomcat,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • 深入详解SpringBoot配置diff的实战方法

    深入详解SpringBoot配置diff的实战方法

    在日常的开发与运维工作中,配置管理是一项至关重要的任务,简单来说,Spring Boot 配置 diff 就是对比 Spring Boot 应用中不同配置版本之间的差异,下面小编就和大家详细介绍一下Spring Boot 配置 diff 的实战方法吧
    2026-04-04
  • 高分面试从Hotspot源码层面剖析java多态实现原理

    高分面试从Hotspot源码层面剖析java多态实现原理

    这篇文章主要为大家介绍了在面试中从Hotspot源码层面来剖析java多态的实现原理,这样回答薪资随你开,有需要的朋友可以借鉴参考下,希望大家多多加薪
    2022-01-01

最新评论