阿里巴巴TransmittableThreadLocal使用指南
前言
ThreadLocal在上下文的数据传输上非常的方便和简洁。工业实践中,比较常用的有三个,ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal,那么他们三个之间有什么区别呢?
常见的三种ThreadLocal比较
| ThreadLocal | InheritableThreadLocal | TransmittableThreadLocal | |
| 来源 | jdk | jdk | 阿里开源 |
| 单线程数据传输 | 支持 | 支持 | 支持 |
| 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);@Overridepublic 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的官网GitCode - 全球开发者的开源社区,开源代码托管平台
相关文章:
阿里巴巴TransmittableThreadLocal使用指南
前言 ThreadLocal在上下文的数据传输上非常的方便和简洁。工业实践中,比较常用的有三个,ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal,那么他们三个之间有什么区别呢? 常见的三种ThreadLocal比较 ThreadLoc…...
ubuntu20下编译linux1.0 (part1)
author: hjjdebug date: 2025年 01月 09日 星期四 15:56:15 CST description: ubuntu20下编译linux1.0 (part1) 该博客记录了新gcc编译旧代码可能碰到的问题和解决办法, 可留作参考 操作环境: ubuntu20 $ gcc --version gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0 $ as --vers…...
欧拉公式和傅里叶变换
注:英文引文机翻,未校。 中文引文未整理去重,如有异常,请看原文。 Euler’s Formula and Fourier Transform Posted byczxttkl October 7, 2018 Euler’s formula states that e i x cos x i sin x e^{ix} \cos{x} i …...
Jenkins内修改allure报告名称
背景: 最近使用Jenkins搭建自动化测试环境时,使用Jenkins的allure插件生成的报告,一直显示默认ALLURE REPORT,想自定义成与项目关联的名称,如图所示,很明显自定义名称显得高大上些,之前…...
30天开发操作系统 第 12 天 -- 定时器 v1.0
前言 定时器(Timer)对于操作系统非常重要。它在原理上却很简单,只是每隔一段时间(比如0.01秒)就发送一个中断信号给CPU。幸亏有了定时器,CPU才不用辛苦地去计量时间。……如果没有定时器会怎么样呢?让我们想象一下吧。 假如CPU看不到定时器而仍想计量时…...
Ubuntu | PostgreSQL | 解决 ERROR: `xmllint` is missing on your system.
解决 sudo apt install apt-file sudo apt-file updatesudo apt-file search xmllint sudo apt install libxml2-utils执行 # postgres源码安装包解压文件夹中 make install make install问题 make -C src install make[2]: Entering directory /home/postgres/postgresql-1…...
uniapp使用chooseLocation安卓篇
本文章全部以高德地图为例 代码 <view class"bottom"><button click"choose">定位</button> </view> choose() {uni.chooseLocation({success: function(res) {console.log(位置名称: res.name);console.log(详细地…...
《PC 上的开源神经网络多模态模型:开启智能交互新时代》
《PC 上的开源神经网络多模态模型:开启智能交互新时代》 一、引言二、多模态模型基础剖析(一)核心概念解读(二)技术架构探秘 三、开源多模态模型的独特魅力(一)开源优势尽显(二&…...
Apache JMeter 压力测试使用说明
文章目录 一、 安装步骤步骤一 下载相关的包步骤二 安装 Jmeter步骤三 设置 Jmeter 工具语言类型为中文 二、使用工具2.1 创建测试任务步骤一 创建线程组步骤二 创建 HTTP 请求 2.2 配置 HTTP 默认参数添加 HTTP消息头管理器HTTP请求默认值 2.3 添加 查看结果监听器2.4 查看结果…...
腾讯云AI代码助手编程挑战赛-知识百科AI
作品简介 知识百科AI这一编程主要用于对于小朋友的探索力的开发,让小朋友在一开始就对学习具有探索精神。在信息化时代下,会主动去学习自己认知以外的知识,同时丰富了眼界,开拓了新的知识。同时催生了在大数据时代下的信息共享化…...
【SpringAOP】Spring AOP 底层逻辑:切点表达式与原理简明阐述
前言 🌟🌟本期讲解关于spring aop的切面表达式和自身实现原理介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 &am…...
HTTP-响应协议
HTTP的响应过程? 浏览器请求数据--》web服务器过程:请求过程 web服务器将响应数据-》到浏览器:响应过程 响应数据有哪些内容? 1.和请求数据类似。 2. 响应体中存储着web服务器返回给浏览器的响应数据。并且注意响应头和响应体之间…...
SQL进阶实战技巧:即时订单比例问题
目录 0 需求描述 1 数据准备 2 问题分析 3 小结 往期精彩 0 需求描述 订单配送中,如果期望配送日期和下单日期相同,称为即时订单,如果期望配送日期和下单日期不同,称为计划订单。 请从配送信息表(delivery_info)中求出每个用户的首单(用户的第一个订单)中即时订单…...
什么是端口
端口是用来区分同一网络设备(IP地址)上运行的不同服务或应用程序接收外部数据的窗口。 以下是几个要点: 对于我们发送请求指定的url中的端口,指的是对方服务器的用于接收数据的端口,如http的80端口,服务器通常都会设定要监听来自…...
【Flutter】使用ScrollController配合EasyRefresh实现列表预加载:在还未滑动到底部时加载下一页数据
需求/背景 在我们的业务场景中,列表的加载使用easy_refresh组件: https://pub.dev/packages/easy_refresh 大概效果是往上滑动到一定的offset会触发一个上滑加载,可以触发一些网络请求拉取列表后面的数据来展示。 这种模式一般在一页翻完…...
【2025 Rust学习 --- 11 实用工具特型01】
清理特型Drop 当一个值的拥有者消失时,Rust 会丢弃(drop)该值。丢弃一个值就必须释放 该值拥有的任何其他值、堆存储和系统资源。 丢弃可能发生在多种情况下: 当变量超出作用域时;在表达式语句的末尾;当…...
网络安全基础以及概念
1. 安全领域的概念 1.1 网络产品 1. EDR:终端检测与响应(Endpoint Detection and Response),终端主要包括我们的笔记本、台式机、手机、服务器等,EDR是一种运行在终端上安全软件,主要负责监控网络流量、可疑进程、注册表活动等其他安全相关的事件与活动。当发现有威胁是自…...
windows和linux的抓包方式
1.实验准备: 一台windows主机,一台linux主机 wireshark使用: 打开wireshark,这些有波动的就代表可以有流量经过该网卡,选择一张有流量经过的网卡 可以看到很多的流量,然后可以使用过滤器来过滤想要的流量…...
【Uniapp-Vue3】v-if条件渲染及v-show的选择对比
如果我们想让元素根据响应式变量的值进行显示或隐藏可以使用v-if或v-show 一、v-show 另一种控制显示的方法就是使用v-show,使用方法和v-if一样,为true显示,为false则不显示。 二、v-if v-if除了可以像v-show一样单独使用外,还…...
宝塔面板使用 GoAccess Web 日志分析教程
宝塔面板是一个简单方便的服务器运维面板,但其网站统计功能是收费的。而 GoAccess 是一个用 C 编写的免费开源 Web日志分析器,本文将介绍如何在宝塔面板中开启 GoAccess Web 日志分析功能。 内容索引 下载安装 GoAccess在宝塔面板中添加日志切割的计划任务将 Web 日志输出到…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...
【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...
