在日常研发过程中,我们经常面临着需要在线程内,线程间进行消息传递,比如在修改一些开源组件源码的过程中,需要将外部参数透传到内部,如果进行方法参数重载,则涉及到的改动量过大,这样,我们可以依赖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 在线程池中进行父子线程间消息传递出现消息丢失的解析的更多相关文章

  1. 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法

    [源码下载] 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法 作者:webabcd 介绍重新想象 Wi ...

  2. C#如何判断线程池中所有的线程是否已经完成之Demo

    start: System.Threading.RegisteredWaitHandle rhw = null; new Action(() => { ; i < ; i++) { new ...

  3. C#如何判断线程池中所有的线程是否已经完成(转)

    其 实很简单用ThreadPool.RegisterWaitForSingleObject方法注册一个定时检查线程池的方法,在检查线程的方法内调用 ThreadPool.GetAvailableThr ...

  4. C#线程学习笔记二:线程池中的工作者线程

    本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/18/ThreadPool.html,记录一下学习过程以备后续查用. 一.线程池基础 首先,创 ...

  5. Java线程池中的核心线程是如何被重复利用的?

    真的!讲得太清楚了!https://blog.csdn.net/MingHuang2017/article/details/79571529 真的是解惑了 本文所说的"核心线程". ...

  6. 线程池中状态与线程数的设计分析(ThreadPoolExecutor中ctl变量)

    目录 预备知识 源码分析 submit()源码分析 shutdownNow()源码分析 代码输出 设计目的与优点 预备知识 可以先看下我的另一篇文章对于Java中的位掩码BitMask的解释. 1.一 ...

  7. 转:专题三线程池中的I/O线程

    上一篇文章主要介绍了如何利用线程池中的工作者线程来实现多线程,使多个线程可以并发地工作,从而高效率地使用系统资源.在这篇文章中将介绍如何用线程池中的I/O线程来执行I/O操作,希望对大家有所帮助. 目 ...

  8. Java并发:搞定线程池(中)

    向线程池提交任务 1.1 execute()     用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功.输入的是一个Runnable实例. public void execute(Ru ...

  9. 【高并发】通过源码深度分析线程池中Worker线程的执行流程

    大家好,我是冰河~~ 在<高并发之--通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程>一文中我们深度分析了线程池执行任务的核心流程,在ThreadPool ...

随机推荐

  1. Elasticsearch 索引策略

    Elasticsearch 7.6 索引生命周期 es的生命周期就对应了索引的策略,比如我们在使用elk的时候,由于数据量较大,时间比较久远的数据就没有那么有价值了,因此就需要定期的清除这些历史数据, ...

  2. k8s 新版本 部署 Ingress-nginx controller

    k8s 新版本 部署 Ingress-nginx controller 本篇主要记录一下 k8s 新版本 1.23.5 中如何搭建 ingress controller 以及里面的注意项 新版本和老版 ...

  3. 类变量_main方法_代码块

    需要解决的问题: 统计一共创建了多少个对象 类变量(静态变量) 被所有对象共享 可以通过 类名(推荐)|对象名.变量名 方式来访问 main main 方法 是虚拟机在调用  args 传值灵活 结果 ...

  4. js 如何判断变量类型

    自定义一些类型 const boolean = true const string = '这是一个字符串' const number = 1 const array = [] const object ...

  5. 手写useState与useEffect

    手写useState与useEffect useState与useEffect是驱动React hooks运行的基础,useState用于管理状态,useEffect用以处理副作用,通过手写简单的us ...

  6. Swift字符串操作-持续更新-2022

    Swift字符串追加 var str = "OC" str.append(" Swfit") print(str) // 输出结果: OC Swift 输出结果 ...

  7. 1.17 想学好Linux,这些习惯必须养成(初学者必读)

    不管是在生活还是工作中,每个人都会逐渐养成一些小习惯.坏习惯一旦形成就很难改正,所在在系统学习 Linux之前,给大家一些建议,刻意去培养一些好的习惯,对自己是很有利的. 学习Linux,要习惯使用命 ...

  8. 【Python情感分析】用python情感分析李子柒频道视频热门评论

    一.事件背景 今天是2021.12.2日,距离李子柒断更已经4个多月了,这是我在YouTube李子柒油管频道上,观看李子柒2021年7月14日上传的最后一条视频,我录制了视频下方的来自全世界各国网友的 ...

  9. 操作系统实现-loader

    博客网址:www.shicoder.top 微信:18223081347 欢迎加群聊天 :452380935 大家好呀,终于我们到了操作系统的loader部分了,loader也是操作系统中最重要的一个 ...

  10. Yapi Docker 部署

    参考 https://github.com/Ryan-Miao/docker-yapi , 并使用该代码的脚本构建yapi image. 部署mongodb docker run \ --name m ...