ThreadLocal父子间通信的四种解决方案
ThreadLocal父子间通信的四种解决方案
ThreadLocal 是存储在线程栈帧中的一块数据存储区域,其可以做到线程与线程之间的读写隔离。
但是在我们的日常场景中,经常会出现父线程需要向子线程中传递消息,而 ThreadLocal 仅能在当前线程上进行数据缓存,这里就介绍4种父子间通信问题;
- 在子线程中手动设置父线程的值
- ThreadPoolTaskExecutor + TaskDecorator
- InheritableThreadLocal
- TransmittableThreadLocal
1.在子线程中手动设置父线程的值
ThreadLocal<String> threadLocal = new ThreadLocal<>();
@BeforeEach
public void init() {
threadLocal.set("thread-01");
}
@Test
public void test4() {
String s = threadLocal.get();
new Thread(() -> {
threadLocal.set(s);
System.out.println(threadLocal.get());
}).start();
threadLocal.remove();
}
在子线程里手动的设置变量,@BeforeEach是junit5的写法,对应junit4的Before
输出结果: thread-01
2.ThreadPoolTaskExecutor + TaskDecorator
使用ThreadPoolTaskExecutor线程池的时候,可自定义一个TaskDecorator包装类,这个类的作用就是在执行子线程之前手动的设置父线程的变量,跟第一种方法类似;
- 储存线程用户信息
public class UserContextUtils {
private static final ThreadLocal<String> userThreadLocal = new ThreadLocal<>();
public static void set(String username) {
userThreadLocal.set(username);
}
public static String get() {
return userThreadLocal.get();
}
public static void clear() {
userThreadLocal.remove();
}
}
- 这是一个执行回调方法的装饰器,主要应用于传递上下文,或者提供任务的监控/统计信息
public class ContextTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
String username = UserContextUtils.get();
return () -> {
try {
// 将主线程的请求信息,设置到子线程中
UserContextUtils.set(username);
// 执行子线程,这一步不要忘了
runnable.run();
} finally {
// 线程结束,清空这些信息,否则可能造成内存泄漏
UserContextUtils.clear();
}
};
}
}
- 初始化线程池
@Bean
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(100);
executor.setAllowCoreThreadTimeOut(false);
executor.setKeepAliveSeconds(0);
executor.setThreadNamePrefix("DefaultAsync-");
executor.setTaskDecorator(new ContextTaskDecorator());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
System.out.println("初始化Async的线程池");
return executor;
}
- 测试
@BeforeEach
public void init() {
threadLocal.set("thread-01");
UserContextUtils.set("taskExecutor");
}
@Resource
private ThreadPoolTaskExecutor getAsyncExecutor;
@Test
public void test3() {
getAsyncExecutor.execute(()->{
System.out.println(UserContextUtils.get());
});
}
使用ThreadPoolTaskExecutor线程池的时候,使用构造器在子线程写入主线程参数,但是使用ThreadPoolExecutor就不能这么做了,建议使用第四种方式TTL;
3.InheritableThreadLocal
inheritableThreadLocal是ThreadLocal中自带的一种方法,只要替换原来的ThreadLocal就行了,但是这种方法有缺陷,会存在核心线程旧值的重复使用,不建议使用;
这里我设置一个线程池,核心线程数为2个,核心线程数重复使用的时候不会重新拿新值,而是用原来的旧值
@Test
public void test1() {
//1.创建一个自己定义的线程池
ExecutorService executorService = new ThreadPoolExecutor(2, 3, 0, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("thread-01");
executorService.execute(() -> {
String s = threadLocal.get();
System.out.println(s);
});
threadLocal.set("thread-02");
executorService.execute(() -> {
String s = threadLocal.get();
System.out.println(s);
});
threadLocal.set("thread-03");
executorService.execute(() -> {
String s = threadLocal.get();
System.out.println(s);
});
threadLocal.set("thread-04");
executorService.execute(() -> {
String s = threadLocal.get();
System.out.println(s);
});
}
测试结果: 因为线程2被重复使用
thread-01
thread-02
thread-02
thread-02
4.TransmittableThreadLocal
TransmittableThreadLocal 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。若希望 TransmittableThreadLocal 在线程池与主线程间传递,需配合 TtlExecutors.getTtlExecutorService,TtlRunnable 和 TtlCallable 使用。
- 引入TTL的jar包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.12.6</version>
</dependency>
- TtlExecutors.getTtlExecutorService()
使用Ttl提供的TtlExecutors.getTtlExecutorService来对原来线程池进行包装,但是此时变量需要使用TransmittableThreadLocal,建议使用这种方式;
@Test
public void test2() {
// 1. 创建一个自己定义的线程池
ExecutorService executorService = new ThreadPoolExecutor(2, 3, 0, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
// 2. 使用TransmittableThreadLocal修饰变量
TransmittableThreadLocal<String> threadLocal1 = new TransmittableThreadLocal<>();
// 3. 使用TtlExecutors.getTtlExecutorService包装线程池
ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(executorService);
threadLocal1.set("thread-01");
ttlExecutorService.execute(() -> {
String s = threadLocal1.get();
System.out.println(s);
});
threadLocal1.set("thread-02");
ttlExecutorService.execute(() -> {
String s = threadLocal1.get();
System.out.println(s);
});
threadLocal1.set("thread-03");
ttlExecutorService.execute(() -> {
String s = threadLocal1.get();
System.out.println(s);
});
threadLocal1.set("thread-04");
ttlExecutorService.execute(() -> {
String s = threadLocal1.get();
System.out.println(s);
});
}
thread-01
thread-02
thread-03
thread-04
- TtlRunnable.get()
@Test
public void test5() {
// 1. 创建一个自己定义的线程池
ExecutorService executorService = new ThreadPoolExecutor(2, 3, 2, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
// 2. 使用TransmittableThreadLocal修饰变量
TransmittableThreadLocal<String> threadLocal1 = new TransmittableThreadLocal<>();
threadLocal1.set("thread-01");
executorService.execute(TtlRunnable.get(() -> {
String s = threadLocal1.get();
System.out.println(s);
}));
threadLocal1.set("thread-02");
executorService.execute(TtlRunnable.get(() -> {
String s = threadLocal1.get();
System.out.println(s);
}));
threadLocal1.set("thread-03");
executorService.execute(TtlRunnable.get(() -> {
String s = threadLocal1.get();
System.out.println(s);
}));
threadLocal1.set("thread-04");
executorService.execute(TtlRunnable.get(() -> {
String s = threadLocal1.get();
System.out.println(s);
}));
}
thread-01
thread-02
thread-03
thread-04
ThreadLocal父子间通信的四种解决方案的更多相关文章
- vue-learning:31 - component - 组件间通信的6种方法
vue组件间通信的6种方法 父子组件通信 prop / $emit 嵌套组件 $attrs / $liteners 后代组件通信 provide / inject 组件实例引用 $root / $pa ...
- Mybatis多参传递的四种解决方案
Mybatis多参传递的四种解决方案 代码异常:org.apache.ibatis.binding.BindingException: Parameter 'param' not found. 长时间 ...
- ios页面间传递参数四种方式
ios页面间传递参数四种方式 1.使用SharedApplication,定义一个变量来传递. 2.使用文件,或者NSUserdefault来传递 3.通过一个单例的class来传递 4.通过Dele ...
- iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用
目的 本文主要是分享iOS多线程的相关内容,为了更系统的讲解,将分为以下7个方面来展开描述. 多线程的基本概念 线程的状态与生命周期 多线程的四种解决方案:pthread,NSThread,GCD,N ...
- 直接将字典转为DataFrame格式时,会出现:ValueError: If using all scalar values, you must pass an index(四种解决方案)
问题:想要将字典直接转为DataFrame格式时,除了字典外没有传入其他参数时,会报错 ValueError: If using all scalar values, you must pass an ...
- vue组件父子间通信之综合练习--假的聊天室
<!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...
- 父子间通信四 ($dispatch 和 $broadcast用法)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- [转] 微信小程序页面间通信的5种方式
微信小程序页面间通的5种方式 PageModel(页面模型)对小程序而言是很重要的一个概念,从app.json中也可以看到,小程序就是由一个个页面组成的. 如上图,这是一个常见结构的小程序:首页是一个 ...
- VC 线程间通信的三种方式
1.使用全局变量(窗体不适用) 实现线程间通信的方法有很多,常用的主要是通过全局变量.自定义消息和事件对象等来实现的.其中又以对全局变量的使用最为简洁.该方法将全局变量作为线程监视的对象,并通 ...
- 【转】VC 线程间通信的三种方式
原文网址:http://my.oschina.net/laopiao/blog/94728 1.使用全局变量(窗体不适用) 实现线程间通信的方法有很多,常用的主要是通过全局变量.自定义消息和 ...
随机推荐
- 微信小程序-获取用户位置
首先我要提供几个文档的链接地址: 首先是官方文档的获取用户位置的API文档地址: 官方文档地址:https://developers.weixin.qq.com/miniprogram/dev/api ...
- HarmonyOS实战[二]—超级详细的原子化服务体验[可编辑的卡片交互]快来尝试吧
相关文章: HarmonyOS实战[一]--原理概念介绍安装:基础篇 [本文正在参与"有奖征文|HarmoneyOS征文大赛"活动] 1.创建HarmonyOS应用 选择Java程 ...
- Azure Data Factory(十二)传参调用 Azure Function
一,引言 在实际的项目中,Azure Data Factroy 中的 Data Flow 并不能彻底帮我们完成一系列复制逻辑计算, 比如我们需要针对数据集的每一行数据进行判断计算,Data Flow ...
- C语言中如何使两个整型变量计算出浮点型结果
遭遇的问题 在学习时有一个课后题要求计算两个变量的加减乘除以及取余,想到除法可能会计算出小数,就用浮点型接收除法的结果 int a,b: double div; div = a / b; 但是算出来的 ...
- 小知识:后台执行Oracle创建索引免受会话中断影响
因为客户环境的堡垒机经常会莫名的断开连接,也不是简单的超时,因为有时候即使你一直在操作,也可能会断. 这样对于操作一些耗时长且中途中断可能会导致异常的操作就很危险,而最简单的避免方法就是将其写到脚本中 ...
- 【译】我为 .NET 开发人员准备的 2023 年 Visual Studio 10 大新功能
原文 | James Montemagno 翻译 | 郑子铭 Visual Studio 2022 在 2023 年发布了许多令人难以置信的功能,为 .NET 开发人员提供了大量新工具来提高他们的工作 ...
- NOI 2023 题解
Copper Loser 的题解-- Day1 T1 方格染色 有一个 \(n\times m\) 的网格,有 \(Q\) 次操作,每次形如有三种:将 \((x_i+j,y_i)\)/\((x_i,y ...
- 从零开始的react入门教程(六),一篇文章理解react组件生命周期
壹 ❀ 引 学习任何一门框架,无论是vue.react亦或是angular,我们除了需要熟练掌握框架语法外,了解框架自身的生命周期也是至关重要的.一方面生命周期在面试中多多少少总是会提及,其次了解框架 ...
- JS leetcode 移除元素 题解分析
壹 ❀ 引 又到了每日一道算法题的环节,今天做的题同样非常简单,题目来源leetcode27. 移除元素,题目描述如下: 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 va ...
- NC16544 简单环
题目链接 题目 题目描述 给定一张n个点m条边的无向图,求出图中所有简单环的数量.(简单环:简单环又称简单回路,图的顶点序列中,除了第一个顶点和最后一个顶点相同外,其余顶点不重复出现的回路叫简单回路. ...