InheritableThreadLocal 在线程池中进行父子线程间消息传递出现消息丢失的解析
在日常研发过程中,我们经常面临着需要在线程内,线程间进行消息传递,比如在修改一些开源组件源码的过程中,需要将外部参数透传到内部,如果进行方法参数重载,则涉及到的改动量过大,这样,我们可以依赖ThreadLocal 来进行消息传递。
ThreadLocal 是 存储在线程栈帧中的一块数据存储区域,其可以做到线程与线程之间的读写隔离。
但是在我们的日常场景中,经常会出现 父线程 需要向子线程中传递消息,而 ThreadLocal 仅能在当前线程上进行数据缓存,因此 我们需要使用 InheritableThreadLocal 来实现 父子线程间的消息传递
// 定义消息
public class ThreadLocalMessage { private final InheritableThreadLocal<Msg> msg; private ThreadLocalMessage() {
msg = new InheritableThreadLocal<>();
} public Msg getMsg() {
return this.msg.get();
} public void setMsg(Msg msg) {
this.msg.set(msg);
} public void clear() {
msg.remove();
} private static final ThreadLocalMessage threadLocalMessage = new ThreadLocalMessage(); public static ThreadLocalMessage getInstance() {
return threadLocalMessage;
} /**
* 获取线程中的消息
*
* @return
*/
public static Msg getOrCreateMsg() {
Msg msg = ThreadLocalMessage.getInstance().getMsg();
if (msg == null) {
msg = new Msg();
}
return msg;
} public static class Msg { /**
* taskId
*/
private String taskId; private Map<String, Object> others; private int retCode; public Msg() {
} public String getTaskId() {
return taskId;
} public void setTaskId(String taskId) {
this.taskId = taskId;
} @Override
public String toString() {
return "Msg{" +
"taskId='" + taskId + '\'' +
", others=" + others +
", retCode=" + retCode +
'}';
}
} }
// 定义线程池
@EnableAsync
@Configuration
public class ExecutorConfig { private final Logger log = LoggerFactory.getLogger(getClass()); @Value("${executor.corePool:2}")
private Integer corePool;
@Value("${executor.maxPool:10}")
private Integer maxPool;
@Value("${executor.queue:2}")
private Integer queue; @Bean("cdl-executor")
public Executor executor() {
log.info("start async Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(corePool);
//配置最大线程数
executor.setMaxPoolSize(maxPool);
//配置队列大小
executor.setQueueCapacity(queue);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("async-executor-"); // 设置拒绝策略
executor.setRejectedExecutionHandler((r, e) -> {
// .....
}); // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
return executor;
// 使用TTL 初始化 executor
//return TtlExecutors.getTtlExecutor(executor);
}
}
// 创建子线程进行消息传递并打印
public String test() throws Exception{
for (int i = 0 ; i < 20; i++){
ThreadLocalMessage.Msg msg = ThreadLocalMessage.getOrCreateMsg();
msg.setTaskId("task_id_"+i);
ThreadLocalMessage.getInstance().setMsg(msg);
myService.testThread(i);
ThreadLocalMessage.getInstance().clear();
}
return "ok";
}
经过代码测试,我们创建了一个池子大小为10 的线程,并发启动了20个线程去进行父子线程消息传递,结果如下:

经过测试,我们发现 只有10个线程 的消息传递成功了,其余10个线程的消息均丢失了,这是什么原因呢。。。
遇到这个问题,我们首先得弄清楚 InheritableThreadLocal 是如何在父子线程间进行消息传递的
InheritableThreadLocal 在父线程创建子线程的时候,会将父线程中InheritableThreadLocal 中存储的数据 拷贝一份 存储到子线程的 InheritableThreadLocal 中
而我们使用的 线程池,线程池是会反复利用线程的,当线程池没有被创建满,每次都是新创建线程,直到线程池创建满了,再需要使用线程就会从线程池中拿已经创建好的线程。
问题就出在这里,由于后面的线程 是从线程池中去捞已经创建好的线程,不会走创建逻辑,也就无法触发 InheritableThreadLocal 中向子线程 拷贝,这也就是为什么 InheritableThreadLocal 合并线程池 使用时,出现了 消息丢失的原因
如何解决????
阿里巴巴开源的TTL ,用于解决线程池中的父子线程复用,线程数据传递,可以完美解决这个问题
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.0.0</version>
</dependency>
@EnableAsync
@Configuration
public class ExecutorConfig { private final Logger log = LoggerFactory.getLogger(getClass()); @Value("${executor.corePool:2}")
private Integer corePool;
@Value("${executor.maxPool:10}")
private Integer maxPool;
@Value("${executor.queue:2}")
private Integer queue; @Bean("cdl-executor")
public Executor executor() {
log.info("start async Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(corePool);
//配置最大线程数
executor.setMaxPoolSize(maxPool);
//配置队列大小
executor.setQueueCapacity(queue);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("async-executor-"); // 设置拒绝策略
executor.setRejectedExecutionHandler((r, e) -> {
// .....
}); // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
// 使用TTL 的 executor
return TtlExecutors.getTtlExecutor(executor);
//return executor;
}
}
public class ThreadLocalMessage {
private final TransmittableThreadLocal<Msg> msg;
private ThreadLocalMessage() {
msg = new TransmittableThreadLocal<>();
}
public Msg getMsg() {
return this.msg.get();
}
public void setMsg(Msg msg) {
this.msg.set(msg);
}
public void clear() {
msg.remove();
}
private static final ThreadLocalMessage threadLocalMessage = new ThreadLocalMessage();
public static ThreadLocalMessage getInstance() {
return threadLocalMessage;
}
/**
* 获取线程中的消息
*
* @return
*/
public static Msg getOrCreateMsg() {
Msg msg = ThreadLocalMessage.getInstance().getMsg();
if (msg == null) {
msg = new Msg();
}
return msg;
}
public static class Msg {
/**
* taskId
*/
private String taskId;
public Msg() {
}
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
@Override
public String toString() {
return "Msg{" +
"taskId='" + taskId + '\'' +
'}';
}
}
}
按照之前的调用方法再试一次,结果如下:

可以发现未出现数据丢失的情况
InheritableThreadLocal 在线程池中进行父子线程间消息传递出现消息丢失的解析的更多相关文章
- 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法
[源码下载] 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法 作者:webabcd 介绍重新想象 Wi ...
- C#如何判断线程池中所有的线程是否已经完成之Demo
start: System.Threading.RegisteredWaitHandle rhw = null; new Action(() => { ; i < ; i++) { new ...
- C#如何判断线程池中所有的线程是否已经完成(转)
其 实很简单用ThreadPool.RegisterWaitForSingleObject方法注册一个定时检查线程池的方法,在检查线程的方法内调用 ThreadPool.GetAvailableThr ...
- C#线程学习笔记二:线程池中的工作者线程
本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/ThreadPool.html,记录一下学习过程以备后续查用. 一.线程池基础 首先,创 ...
- Java线程池中的核心线程是如何被重复利用的?
真的!讲得太清楚了!https://blog.csdn.net/MingHuang2017/article/details/79571529 真的是解惑了 本文所说的"核心线程". ...
- 线程池中状态与线程数的设计分析(ThreadPoolExecutor中ctl变量)
目录 预备知识 源码分析 submit()源码分析 shutdownNow()源码分析 代码输出 设计目的与优点 预备知识 可以先看下我的另一篇文章对于Java中的位掩码BitMask的解释. 1.一 ...
- 转:专题三线程池中的I/O线程
上一篇文章主要介绍了如何利用线程池中的工作者线程来实现多线程,使多个线程可以并发地工作,从而高效率地使用系统资源.在这篇文章中将介绍如何用线程池中的I/O线程来执行I/O操作,希望对大家有所帮助. 目 ...
- Java并发:搞定线程池(中)
向线程池提交任务 1.1 execute() 用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功.输入的是一个Runnable实例. public void execute(Ru ...
- 【高并发】通过源码深度分析线程池中Worker线程的执行流程
大家好,我是冰河~~ 在<高并发之--通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程>一文中我们深度分析了线程池执行任务的核心流程,在ThreadPool ...
随机推荐
- 2021.12.19 eleveni的刷题记录
2021.12.19 eleveni的刷题记录 0. 本次记录有意思的题 0.1 每个点恰好经过一次并且求最小时间 P2469 [SDOI2010]星际竞速 https://www.luogu.com ...
- ubuntu16.04安装MATLAB R2017b步骤详解(附完整文件包)
摘要:介绍在ubuntu16.04中从下载到安装成功的完整步骤.本文给出MATLAB R2017b(Linux系统)的完整安装包百度云盘下载地址,逐步介绍一种简单易行的安装方法,在桌面创建快捷方式,最 ...
- 数据建模软件Chiner,颜值与实用性并存
目录 一.chiner介绍 二.值得关注的功能点 2.1. 兼容各种格式的数据建模文件 2.2. 支持多数据库.代码生成 2.3. 支持逻辑视图与物理视图设计 2.4. 自动生成数据库文档 三.总结 ...
- 5. VIM编辑器
Vim编辑器中设置了3种模式-命令模式.末行模式和编辑模式 命令模式:控制光标移动,可对文本进行复制.粘贴.删除和查找等工作. 输入模式:正常的文本录入. 末行模式:保存或退出文档,以及设置编辑环境. ...
- python学习-Day16
目录 今日内容详细 内置函数补充 常见内置函数 help() id() int() isinstance() pow() round() sum() 求和 迭代器 可迭代对象 什么是可迭代对象? 哪些 ...
- R2DBC正式孵化成功,利好Spring Webflux
2022年4月25日,R2DBC社区宣布具有普遍可用性的1.0.0.RELEASE正式发布. R2DBC致力于为反应式编程 API操作关系型数据库带来规范支持,R2DBC不同于我们熟知的JDBC规范, ...
- Linux文本工具-cat-cut-paste;文本分析-sort-wc-uniq
1.1 查看文本文件内容 cat 1.1.1 cat可以查看文本内容 cat [OPTION]... [FILE]... 常见选项 -E: 显示行结束符$ -A: 显示所有控制符 -n: 对显示出的 ...
- 手脱NsPacK壳
1.查壳 使用PEiD未能检测到壳信息,这时,我们更换其他工具 从图中可以看到壳的信息为[NsPacK(3.x)[-]] 2.百度壳信息 北斗程序压缩(Nspack)是一款压缩壳.主要的选项是:压缩资 ...
- SSO 方案演进
背景介绍 随着业务与技术的发展,现今比以往任何时候都更需要单点登录 SSO 身份验证. 现在几乎每个网站都需要某种形式的身份验证才能访问其功能和内容. 随着网站和服务数量的增加,集中登录系统已成为一种 ...
- 力扣算法JS LC 59-螺旋矩阵2,LC 152-乘积最大子数组
LC 59-螺旋矩阵2 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix . 示例 1: 输入:n = 3输出:[[1,2 ...