当前位置:首页 > 科技  > 软件

基于TTL 解决线程池中 ThreadLocal 线程无法共享的问题

来源: 责编: 时间:2024-04-08 17:19:23 296观看
导读在Java的并发编程领域中,ThreadLocal被广泛运用来解决线程安全困境,它巧妙地为每个线程提供独立的变量副本,有效规避了线程间数据共享的问题。不过,在使用线程池时,传递线程局部变量在父子线程之间并非易事。这是因为Threa

在Java的并发编程领域中,ThreadLocal被广泛运用来解决线程安全困境,它巧妙地为每个线程提供独立的变量副本,有效规避了线程间数据共享的问题。Blt28资讯网——每日最新资讯28at.com

不过,在使用线程池时,传递线程局部变量在父子线程之间并非易事。这是因为ThreadLocal的设计初衷仅在于线程内的数据隔离,无法支持跨线程间的数据传递。Blt28资讯网——每日最新资讯28at.com

背景

在基于Java的应用开发领域,尤其是在利用Spring框架、异步处理和微服务架构构建系统时,常常需要在不同线程或服务之间传递用户会话、数据库事务或其他上下文信息。Blt28资讯网——每日最新资讯28at.com

举例来说,在处理用户请求的Web服务中,记录日志是必不可少的一环。这些日志需包含请求的独特标识(如请求ID),这个ID在请求进入服务时生成,并会贯穿整个处理流程,包括可能并发执行的多个子任务或被分配到线程池中不同线程上执行。(在分布式场景中通常会称之为traceId)Blt28资讯网——每日最新资讯28at.com

在这种情况下,使用ThreadLocal来存储请求ID会带来问题:并发执行的子任务无法访问父线程ThreadLocal中存储的请求ID,而且在使用线程池时,线程的重用可能导致请求ID被错误地共享或丢失。Blt28资讯网——每日最新资讯28at.com

伪代码:Blt28资讯网——每日最新资讯28at.com

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ThreadLocalExample {        private static ThreadLocal<String> requestId = new ThreadLocal<>();    public static void main(String[] args) {        requestId.set("12345"); // 设置请求ID        ExecutorService executor = Executors.newFixedThreadPool(2);        executor.submit(() -> {            System.out.println("Child task running in a separate thread: " + requestId.get());        });        executor.shutdown();    }}

在这个示例中,父线程设置了请求ID为"12345",但是当子任务在另一个线程中执行时,无法访问到父线程中的ThreadLocal变量requestId,因此子任务无法获取到请求ID,可能会输出null或者""。Blt28资讯网——每日最新资讯28at.com

伪代码:Blt28资讯网——每日最新资讯28at.com

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ThreadLocalThreadPoolExample {        private static ThreadLocal<String> requestId = new ThreadLocal<>();    public static void main(String[] args) {        requestId.set("12345"); // 设置请求ID        ExecutorService executor = Executors.newFixedThreadPool(2);        executor.submit(() -> {            System.out.println("Child task running in a thread pool: " + requestId.get());        });        // 另一个任务复用线程        executor.submit(() -> {            System.out.println("Another child task running in the same thread: " + requestId.get());        });        executor.shutdown();    }}

在这个示例中,如果线程池中的两个任务在同一个线程中执行,且没有正确处理ThreadLocal变量,可能会导致第二个任务获取到了第一个任务的请求ID,导致请求ID的错误共享。Blt28资讯网——每日最新资讯28at.com

技术选型

为了应对这一难题,可以采用TransmittableThreadLocal(TTL)这一阿里巴巴开源工具库,专为解决在使用线程池等会重用线程的情况下,ThreadLocal无法正确管理线程上下文的问题而设计。Blt28资讯网——每日最新资讯28at.com

GitHub开源地址:https://github.com/alibaba/transmittable-thread-localBlt28资讯网——每日最新资讯28at.com

TransmittableThreadLocal基于ThreadLocal进行扩展,提供了跨线程传递数据的能力,确保父线程传递值给子线程,并支持线程池等场景下的线程数据隔离。Blt28资讯网——每日最新资讯28at.com

此外,还有JDK自带的InheritableThreadLocal,用于主子线程间参数传递。然而,这种方式存在一个限制:必须在主线程手动创建子线程才可使用,而在线程池中则难以实现此种传递机制。Blt28资讯网——每日最新资讯28at.com

具体实现

依赖引入

首先,需在项目中引入TransmittableThreadLocal的依赖。若为Maven项目,可添加以下依赖:Blt28资讯网——每日最新资讯28at.com

<dependency>  <groupId>com.alibaba</groupId>  <artifactId>transmittable-thread-local</artifactId>  <version><!-- 使用最新版本 --></version> </dependency>

使用TransmittableThreadLocal存储请求ID

public class RequestContext {    // 使用TransmittableThreadLocal来存储请求ID    private static final ThreadLocal<String> requestIdTL = new TransmittableThreadLocal<>();    public static void setRequestId(String requestId) {        requestIdTL.set(requestId);    }    public static String getRequestId() {        return requestIdTL.get();    }    public static void clear() {        requestIdTL.remove();    }}
创建一个线程池,并使用TTL提供的工具类确保线程池兼容TransmittableThreadLocal
import com.alibaba.ttl.threadpool.TtlExecutors;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ThreadPoolUtil {    private static final ExecutorService pool = Executors.newFixedThreadPool(10);    // 使用TtlExecutors工具类包装原始的线程池,使其兼容TransmittableThreadLocal    public static final ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(pool);    public static ExecutorService getExecutorService() {        return ttlExecutorService;    }}

TtlExecutors是TransmittableThreadLocal(TTL)库中的一款实用工具类,其机制在于对Java标准库中的ExecutorService、ScheduledExecutorService等线程池接口的实例进行包装。Blt28资讯网——每日最新资讯28at.com

通过这种封装,确保在使用线程池时,能够正确地传递TransmittableThreadLocal中存储的上下文数据,即使任务在不同线程中执行。这对于解决在使用线程池时ThreadLocal变量值传递的问题至关重要。Blt28资讯网——每日最新资讯28at.com

执行并行任务,并在任务中使用RequestContext来访问请求ID
import java.util.stream.IntStream;public class Application {    public static void main(String[] args) {        // 模拟Web应用中为每个请求设置唯一的请求ID        String requestId = "REQ-" + System.nanoTime();        RequestContext.setRequestId(requestId);        try {            ExecutorService executorService = ThreadPoolUtil.getExecutorService();            IntStream.range(0, 5).forEach(i ->                 executorService.submit(() -> {                    // 在子线程中获取并打印请求ID                    System.out.println("Task " + i + " running in thread " + Thread.currentThread().getName() + " with Request ID: " + RequestContext.getRequestId());                })            );        } finally {            // 清理资源            RequestContext.clear();            ThreadPoolUtil.getExecutorService().shutdown();        }    }}


Blt28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-82037-0.html基于TTL 解决线程池中 ThreadLocal 线程无法共享的问题

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 超越GPT4的Agent,我用代码实现了!

下一篇: Go 哪里没有做好?Rob Pike 深刻反思了

标签:
  • 热门焦点
  • 三言两语说透设计模式的艺术-单例模式

    写在前面单例模式是一种常用的软件设计模式,它所创建的对象只有一个实例,且该实例易于被外界访问。单例对象由于只有一个实例,所以它可以方便地被系统中的其他对象共享,从而减少
  • 如何通过Python线程池实现异步编程?

    线程池的概念和基本原理线程池是一种并发处理机制,它可以在程序启动时创建一组线程,并将它们置于等待任务的状态。当任务到达时,线程池中的某个线程会被唤醒并执行任务,执行完任
  • 新电商三兄弟,“抖快红”成团!

    来源:价值研究所作 者:Hernanderz 随着内容电商的概念兴起,抖音、快手、小红书组成的&ldquo;新电商三兄弟&rdquo;成为业内一股不可忽视的势力,给阿里、京东、拼多多带去了巨大压
  • 大厂卷向扁平化

    来源:新熵作者丨南枝 编辑丨月见大厂职级不香了。俗话说,兵无常势,水无常形,互联网企业调整职级体系并不稀奇。7月13日,淘宝天猫集团启动了近年来最大的人力制度改革,目前已形成一
  • 三星Galaxy Z Fold5今日亮相:厚度缩减但仍略显厚重

    据官方此前宣布,三星将于7月26日也就是今天在韩国首尔举办Unpacked活动,届时将带来带来包括Galaxy Buds 3、Galaxy Watch 6、Galaxy Tab S9、Galaxy
  • iQOO Neo8 Pro抢先上架:首发天玑9200+ 安卓性能之王

    经过了一段时间的密集爆料,昨日iQOO官方如期对外宣布:将于5月23日推出全新的iQOO Neo8系列新品,官方称这是一款拥有旗舰级性能调校的作品。随着发布时
  • OPPO K11样张首曝:千元机影像“卷”得真不错!

    一直以来,OPPO K系列机型都保持着较为均衡的产品体验,历来都是2K价位的明星机型,去年推出的OPPO K10和OPPO K10 Pro两款机型凭借各自的出色配置,堪称有
  • Windows 11发布,微软一改往常对老机型开放的态度

    距离 Windows 11 发布已经过去一周,在过去一周里,很多数码爱好者围绕其对 Android 应用的支持、对老机型的升级问题展开了激烈讨论。与以往不同的是,在这次大
  • 2022爆款:ROG魔霸6 冰川散热系统持续护航

    喜逢开学季,各大商家开始推出自己的新产品,进行打折促销活动。对于忠实的端游爱好者来说,能够拥有一款梦寐以求的笔记本电脑是一件十分开心的事。但是现在的
Top