在日常研发过程中,我们经常面临着需要在线程内,线程间进行消息传递,比如在修改一些开源组件源码的过程中,需要将外部参数透传到内部,如果进行方法参数重载,则涉及到的改动量过大,这样,我们可以依赖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. IDEA小技巧:Markdown里的命令行可以直接运行了

    作为一名开发者,相信大部分人都喜欢用Markdown来写文章和写文档. 如果你经常用开源项目或者自己维护开源项目,肯定对于项目下的README文件也相当熟悉了吧,通常我们会在这里介绍项目的功能.如何使 ...

  2. Spring的3级缓存和循环引用的理解

    此处是我自己的一个理解,防止以后忘记,如若那个地方理解不对,欢迎指出. 一.背景 在我们写代码的过程中一般会使用 @Autowired 来注入另外的一个对象,但有些时候发生了 循环依赖,但是我们的代码 ...

  3. Apache Flink系列-④有状态函数

    有状态函数:独立于平台的有状态无服务器堆栈   这是一种在现代基础设施上创建高效.可扩展且一致的应用程序的简单方法,无论规模大小.   有状态函数是一种API,它通过为无服务器架构构建的运行时简化了分 ...

  4. 『现学现忘』Git基础 — 14、Git基础操作的总结与补充

    目录 1.Git本地版本库结构 2.Git常用操作方法 3.补充:添加多个文件到暂存区 4.补充:提交操作未写备注 5.补充:从工作区直接提交到版本库 1.Git本地版本库结构 如下图所示: 工作区( ...

  5. vue项目中的去抖与节流

    节流 // fn是我们需要包装的事件回调, interval是时间间隔的阈值 function throttle(fn, interval) { let last = 0; // last为上一次触发 ...

  6. ThinkPhP $map用法

    ThinkPHP内置了非常灵活的查询方法,可以快速的进行数据查询操作,查询条件可以用于CURD等任何操作,作为where方法的参数传入即可,下面来一一讲解查询语言的内涵.查询方式ThinkPHP可以支 ...

  7. C# iText 7 切分PDF,处理PDF页面大小

    一.itext 我要使用itext做一个pdf的页面大小一致性处理,然后再根据数据切分出需要的pdf. iText的官网有关于它的介绍,https://itextpdf.com/ 然后在官网可以查找a ...

  8. netty系列之:netty中的自动解码器ReplayingDecoder

    目录 简介 ByteToMessageDecoder可能遇到的问题 ReplayingDecoder的实现原理 总结 简介 netty提供了一个从ByteBuf到用户自定义的message的解码器叫做 ...

  9. keil工程当中实现printf重定向串口打印

    之前是完全不知道printf可以重定向设置 最近才发现还有这等好事,可以让printf直接实现串口打印 在网上找了很多资料,终于实现了我想要的效果 原理:printf是通过调用底部的fputc来实现打 ...

  10. 面试官问:浏览器输入 URL 回车之后发生了什么?

    一个执着于技术的公众号 前言 这个问题已经是老生常谈了,更是经常被作为面试的压轴题出现,网上也有很多文章,但最近闲的无聊,然后就自己做了一篇笔记,感觉比之前理解更透彻了. 注意:本文的步骤是建立在,请 ...