小工具      在线工具  汉语词典  css  js  c++  java

ThreadLocal父子通信的四种解决方案

Java基础,java 额外说明

收录于:40天前

ThreadLocal父子间通信的四种解决方案

ThreadLocal是存储在线程栈帧中的数据存储区域,可以实现线程间的读写隔离。

然而,在我们的日常场景中,经常会出现父线程需要向子线程传递消息的情况,而ThreadLocal只能缓存当前线程上的数据。以下是4类亲子沟通问题;

  • 在子线程中手动设置父线程的值
  • ThreadPoolTask​​Executor + TaskDecorator
  • 可继承线程本地
  • 可传输线程本地

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

使用ThreadPoolTask​​Executor线程池时,可以自定义一个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());
  });
}

使用ThreadPoolTask​​Executor线程池时,使用构造函数在子线程中写入主线程参数。但是,使用 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

可传输线程本地 是Alibaba开源的、用于解决 “使用缓存线程的线程池等组件时传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。若希望 TransmittableThreadLocal 在线程池与主线程间传递,需配合 TtlExecutors.getTtlExecutorService,ttl可运行可调用的Ttl 使用。

  • 引入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
. . .

相关推荐

额外说明

高并发怎么保证幂等

前端 让用户只点击一次 比如点击后按钮变灰,或用loading显示 RPG重定向 就是 Post--redirect-Get ,当提交表单后,去执行一个客户端的重定向,转到提交成功的页面,这样就避免了用户F5刷新导致重复提交,也能消除浏览器后退导致重复提

额外说明

JSP标准标签库

简介 JSTL是一个不断完善的开放源代码的JSP标签库,是由apache的jakarta小组来维护的。JSTL只能运行在支持JSP1.2和Servlet2.3规范的容器上,如tomcat 4.x。但是在即将推出的JSP 2.0中是作为标准支持的。 JST

额外说明

企业级实战——品优购电商系统开发- 88. 89 . 自定义登录页 退出

QQ 1274510382 Wechat JNZ_aming 商业联盟 QQ群538250800 技术搞事 QQ群599020441 解决方案 QQ群152889761 加入我们 QQ群649347320 共享学习 QQ群674240731 纪年科技am

额外说明

mybatis-学习笔记

小伙伴们,你们好呀!我是老寇! 以下学习资料来源于《深入浅出MyBatis技术原理与实战》 目录 一、我的笔记 一.mybatis、hibernate及传统jdbc的对比? 1.1 传统jdbc连接数据库经过的过程 1.2 传统jdbc的弊端 2.1 h

额外说明

编译原理(龙书):第三章部分题目参考答案

目录 3.1.1 3.1.2 3.3.2 3.3.5 3.3.12 3.4.1 3.4.2 3.6.2 3.6.3 3.6.4 3.7.1 3.7.3 3.1.1 3.1.2 3.3.2 3.3.5 3.3.12 3.4.1 3.4.2   3.6.2

额外说明

微服务生态系统:使用Spring Cloud构建分布式系统

文章目录 什么是微服务? 为什么选择Spring Cloud? Spring Cloud的关键组件 示例:构建一个简单的微服务 步骤1:创建Spring Boot项目 步骤2:配置Eureka服务发现 步骤3:创建REST控制器 步骤4:运行项目 步骤5

额外说明

Part II : 4_外观模式

定义: 外观模式 内容: 为子系统中的 一组接口提供一个一致的界面, 外观模式定义了一个 高层接口,这个接口使得这一子系统更加容易使用; 角色 外观 facade; 子系统类; subsystem classes ; 优点:

额外说明

Java Web应用小案例:猜数小游戏

Java Web应用小案例:猜数小游戏 文章目录 一、演示Python版猜数游戏 二、JSP版猜数游戏程序运行效果

额外说明

Linux-命令终端提示符显示-bash-4.1#解决方法

问题描述: root用户登录之后,Linux命令提示符显示 bash-4.1#。 问题原因: 出现这个问题的原因是由于某些误操作导致root目录下的 .bash_profile 和 .bashrc 这两个文件被删除 。 问题解决: 将/etc/skel

额外说明

阿里云短信及直播操作说明

阿里云短信接口操作说明 如何创建和查看AccessKey 后台设置 阿里云直播接口操作说明 后台设置 前台使用 graph LR 创建课程-->设置课程人数 设置课程人数-->课时管理 课时管理-->添加课时 添加课时-->直播 直播-->保存 保存--

ads via 小工具