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,TtlRunnableTtlCallable 使用。

  • 引入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父子间通信的四种解决方案的更多相关文章

  1. vue-learning:31 - component - 组件间通信的6种方法

    vue组件间通信的6种方法 父子组件通信 prop / $emit 嵌套组件 $attrs / $liteners 后代组件通信 provide / inject 组件实例引用 $root / $pa ...

  2. Mybatis多参传递的四种解决方案

    Mybatis多参传递的四种解决方案 代码异常:org.apache.ibatis.binding.BindingException: Parameter 'param' not found. 长时间 ...

  3. ios页面间传递参数四种方式

    ios页面间传递参数四种方式 1.使用SharedApplication,定义一个变量来传递. 2.使用文件,或者NSUserdefault来传递 3.通过一个单例的class来传递 4.通过Dele ...

  4. iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用

    目的 本文主要是分享iOS多线程的相关内容,为了更系统的讲解,将分为以下7个方面来展开描述. 多线程的基本概念 线程的状态与生命周期 多线程的四种解决方案:pthread,NSThread,GCD,N ...

  5. 直接将字典转为DataFrame格式时,会出现:ValueError: If using all scalar values, you must pass an index(四种解决方案)

    问题:想要将字典直接转为DataFrame格式时,除了字典外没有传入其他参数时,会报错 ValueError: If using all scalar values, you must pass an ...

  6. vue组件父子间通信之综合练习--假的聊天室

    <!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. 父子间通信四 ($dispatch 和 $broadcast用法)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. [转] 微信小程序页面间通信的5种方式

    微信小程序页面间通的5种方式 PageModel(页面模型)对小程序而言是很重要的一个概念,从app.json中也可以看到,小程序就是由一个个页面组成的. 如上图,这是一个常见结构的小程序:首页是一个 ...

  9. VC 线程间通信的三种方式

    1.使用全局变量(窗体不适用)     实现线程间通信的方法有很多,常用的主要是通过全局变量.自定义消息和事件对象等来实现的.其中又以对全局变量的使用最为简洁.该方法将全局变量作为线程监视的对象,并通 ...

  10. 【转】VC 线程间通信的三种方式

    原文网址:http://my.oschina.net/laopiao/blog/94728 1.使用全局变量(窗体不适用)      实现线程间通信的方法有很多,常用的主要是通过全局变量.自定义消息和 ...

随机推荐

  1. 5.0 Python 定义并使用函数

    函数是python程序中的基本模块化单位,它是一段可重用的代码,可以被多次调用执行.函数接受一些输入参数,并且在执行时可能会产生一些输出结果.函数定义了一个功能的封装,使得代码能够模块化和组织结构化, ...

  2. webrtc终极版(题外话)辛苦写文章分享,竟然遇到喷子狂喷,写篇文章回怼下,顺便发表下面对喷子的处理方式

    webrtc终极版(题外话)辛苦写文章分享,竟然遇到喷子狂喷,写篇文章回怼下,顺便发表下面对喷子的处理方式 第一篇文章发过后,出人意料的是,收到了博客园某一位用户的狂喷[注:本系列文章会同步发布到cs ...

  3. 【题解】P9749 [CSP-J 2023] 公路

    \(Meaning\) \(Solution\) 这道题我来讲一个不一样的解法:\(dp\) 在写 \(dp\) 之前,我们需要明确以下几个东西:状态的表示,状态转移方程,边界条件和答案的表示. 状态 ...

  4. ASP.NET Core分布式项目实战(详解oauth2授权码流程)--学习笔记

    最近公司产品上线,通宵加班了一个月,一直没有更新,今天开始恢复,每日一更,冲冲冲 任务13:详解oauth2授权码流程 我们即将开发的产品有一个用户 API,一个项目服务 API,每个服务都需要认证授 ...

  5. PCG——程序化地形生成(1)

    前言 接触了半年多Houdini,佛系研究了一下PCG(Procedural Content Generation)相关的技术,这真是个好东西,赶在年前写个总结.Houdini 一款DCC软件,功能又 ...

  6. SP21690 POWERUP - Power the Power Up 题解

    题目传送门 前置知识 扩展欧拉定理 解法 直接对 \(a\) 和 \(b^c\) 分讨,跑一遍扩展欧拉定理就行了. 另外由于本题的特殊规定 \(0^0=1\),故需要在当 \(a=0\) 时,对 \( ...

  7. JS 这一次彻底理解插入排序

    壹 ❀ 引 在前两篇排序文章中,我们分别介绍了冒泡排序与选择排序,趁热打铁,我们接着聊插入排序.老实说,在分析排序过程中头脑很清楚,过后再尝试写出排序代码还有点坎坷...可能是我脑瓜子不太机灵的问题, ...

  8. spring boot中配置网页语言国际化

    项目地址:https://gitee.com/indexman/spring_boot_in_action 开发步骤 1.编写国际化配置文件 场景是给登录页面 login.html添加国际化支持. 2 ...

  9. 利用BARK和Telebot进行VPS实时预警推送

    前言 在服务器的日常维护和蓝队的日常监控中,经常需要对服务器出现的各种问题进行及时的预警推送.国外的服务器推荐使用telebot,而国内由于特殊的网络环境,则推荐使用BARK.Chanify等进行推送 ...

  10. zip压缩模块,tarfile压缩模块,包和模块,format格式化的复习--day17

    1.zipfile模块 import zipfile #导入模块 1.压缩文件 (1)创建压缩包 参数1压缩包名字,参数2以w模式创建,参数3压缩固定写法 zf = zipfile.ZipFile(& ...