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

Java 新技术:虚拟线程使用指南

来源: 责编: 时间:2024-01-15 09:21:07 298观看
导读虚拟线程是在 Java 21 版本中实现的一种轻量级线程。它由 JVM 进行创建以及管理。虚拟线程和传统线程(我们称之为平台线程)之间的主要区别在于,我们可以轻松地在一个 Java 程序中运行大量、甚至数百万个虚拟线程。本文是

虚拟线程是在 Java 21 版本中实现的一种轻量级线程。它由 JVM 进行创建以及管理。虚拟线程和传统线程(我们称之为平台线程)之间的主要区别在于,我们可以轻松地在一个 Java 程序中运行大量、甚至数百万个虚拟线程。eYS28资讯网——每日最新资讯28at.com

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

本文是继《Java 21 新技术:虚拟线程使用指南》的第二篇文章,无意全面涵盖虚拟线程的每个重要细节,目的是给大家使用虚拟线程提供一套使用指南,帮助大家能更好使用的虚拟线程,发挥其作用并避免踩坑。eYS28资讯网——每日最新资讯28at.com

本文完整大纲如下,eYS28资讯网——每日最新资讯28at.com

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

使用信号量限制并发

在某些场景下,我们需要限制某个操作的并发数。例如某些外部服务可能无法同时处理超过 10 个并发请求。eYS28资讯网——每日最新资讯28at.com

由于平台线程是一种宝贵的资源,通常在线程池中进行管理,因此线程池的使用对于如今的程序员相当普遍。eYS28资讯网——每日最新资讯28at.com

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

比如上面例子要限制并发请求数,某些人会使用线程池来处理,代码如下,eYS28资讯网——每日最新资讯28at.com

ExecutorService es = Executors.newFixedThreadPool(10);...Result foo() {    try {        var fut = es.submit(() -> callLimitedService());        return f.get();    } catch (...) { ... }}

上面代码示例可以确保外部服务最多只有 10 个并发请求,因为我们的线程池中只有最多 10 个线程。eYS28资讯网——每日最新资讯28at.com

限制并发只是使用线程池的副产品。线程池旨在共享稀缺资源,而虚拟线程并不稀缺,因此永远不应该池化虚拟线程!eYS28资讯网——每日最新资讯28at.com

使用虚拟线程时,如果要限制访问某些服务的并发请求,则应该使用专门为此目的设计的 Semaphore 类。示例代码如下,eYS28资讯网——每日最新资讯28at.com

Semaphore sem = new Semaphore(10);...Result foo() {    sem.acquire();    try {        return callLimitedService();    } finally {        sem.release();    }}

在这个示例中,同一时刻只有 10 个虚拟线程可以进入 foo() 方法取得锁,而其他虚拟线程将会被阻塞。eYS28资讯网——每日最新资讯28at.com

简单地使用信号量阻塞某些虚拟线程可能看起来与将任务提交到固定数量线程池有很大不同,但事实并非如此。eYS28资讯网——每日最新资讯28at.com

将任务提交到等待任务池会将它们排队处理,信号量在内部(或任何其他阻塞同步构造)构造了一个阻塞线程队列,这些任务在阻塞线程队列上也会进行排队处理。eYS28资讯网——每日最新资讯28at.com

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

我们可以将平台线程池认作是从等待任务队列中提取任务进行处理的工作人员,然后将虚拟线程视为任务本身,在任务或者线程可以执行之前将会被阻塞,但任务或者线程被阻塞时在计算机中的底层表示上实际是相同的。eYS28资讯网——每日最新资讯28at.com

这里想告诉大家的就是不管是线程池的任务排队,还是信号量内部的线程阻塞,它们之间是由等效性的。在虚拟线程某些需要限制并发数场景下,直接使用信号量即可。eYS28资讯网——每日最新资讯28at.com

不要在线程局部变量中缓存可重用对象

虚拟线程支持线程局部变量,就像平台线程一样。通常线程局部变量用于将一些特定于上下文的信息与当前运行的代码关联起来,例如当前事务和用户 ID。eYS28资讯网——每日最新资讯28at.com

对于虚拟线程来说,使用线程局部变量是完全合理的。但是如果考虑更安全、更有效的线程局部变量,可以使用 Scoped Values。eYS28资讯网——每日最新资讯28at.com

更多有关 Scoped Values 介绍,请参阅 https://docs.oracle.com/en/java/javase/21/core/scoped-values.html#GUID-9A4565C5-82AE-4F03-A476-3EAA9CDEB0F6eYS28资讯网——每日最新资讯28at.com

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

线程局部变量有一种用途与虚拟线程是不太适合的,那就是缓存可重用对象。eYS28资讯网——每日最新资讯28at.com

可重用对象的创建成本通常很高,通常消耗大量内存且可变,还不是线程安全的。它们被缓存在线程局部变量中,以减少它们实例化的次数以及它们在内存中的实例数量,好处是它们可以被线程上不同时间运行的多个任务重用,减少昂贵对象创建的开销。eYS28资讯网——每日最新资讯28at.com

例如 SimpleDateFormat 的实例创建成本很高,而且不是线程安全的。为了解决创建成本、线程不安全问题,通常是将此类实例缓存在 ThreadLocal 中,如下例所示:eYS28资讯网——每日最新资讯28at.com

static final ThreadLocal<SimpleDateFormat> cachedFormatter =       ThreadLocal.withInitial(SimpleDateFormat::new);void foo() {  ... cachedFormatter.get().format(...); ...}

仅当线程(以及因此在线程本地缓存的昂贵对象)被多个任务共享和重用时(就像平台线程被池化时的情况一样),这种缓存才有用。许多任务在线程池中运行时可能会调用 foo,但由于池中仅包含几个线程,因此该对象只会被实例化几次(每个池线程一次)并被缓存和重用。eYS28资讯网——每日最新资讯28at.com

但是虚拟线程永远不会被池化,也不会被不相关的任务重用。因为每个任务都有自己的虚拟线程,所以每次从不同任务调用 foo 都会触发新 SimpleDateFormat 的实例化。而且由于可能有大量的虚拟线程同时运行,昂贵的对象可能会消耗相当多的内存。这些结果与线程本地缓存想要实现的结果恰恰相反。eYS28资讯网——每日最新资讯28at.com

对于线程局部变量缓存可重用对象的问题,没有什么好的通用替代方案,但对于 SimpleDateFormat,我们应该将其替换为 DateTimeFormatter。DateTimeFormatter 是不可变的,因此单个实例就可以由所有线程共享:eYS28资讯网——每日最新资讯28at.com

static final DateTimeFormatter formatter = DateTimeFormatter….;void foo() {  ... formatter.format(...); ...}

需要注意的是,使用线程局部变量来缓存共享的昂贵对象有时是由一些异步框架在幕后完成的,其隐含的假设是这些可重用对象只会由极少数池线程使用。eYS28资讯网——每日最新资讯28at.com

所以混合虚拟线程和异步框架一起使用可能不是一个好主意,对某些方法的调用可能会导致可重用对象被重复创建。eYS28资讯网——每日最新资讯28at.com

避免长时间和频繁的 synchronized

当前虚拟线程实现由一个限制是,在同步块或方法内执行 synchronized 阻塞操作会导致 JDK 的虚拟线程调度程序阻塞宝贵的操作系统线程,而如果阻塞操作是在同步块或方法外完成的,则不会被阻塞。我们称这种情况为 “Pinning”。eYS28资讯网——每日最新资讯28at.com

如果阻塞操作既长期又频繁,则 “Pinning” 可能会对服务器的吞吐量产生不利影响。如果阻塞操作短暂(例如内存中操作)或不频繁则可能不会产生不利影响。eYS28资讯网——每日最新资讯28at.com

为了检测可能有害的 “Pinning” 实例,(JDK Flight Recorder (JFR) 在 “Pinning” 阻塞时间超过 20 毫秒时,会发出 jdk.VirtualThreadPinned 事件。eYS28资讯网——每日最新资讯28at.com

或者我们可以使用系统属性 jdk.tracePinnedThreads 在线程被 “Pinning” 阻塞时发出堆栈跟踪。eYS28资讯网——每日最新资讯28at.com

启动 Java 程序时添加 -Djdk.tracePinnedThreads=full 运行,会在线程被 “Pinning” 阻塞时打印完整的堆栈跟踪,突出显示本机帧和持有监视器的帧。使用 -Djdk.tracePinnedThreads=short 运行,会将输出限制为仅有问题的帧。eYS28资讯网——每日最新资讯28at.com

如果这些机制检测到既长期又频繁 “Pinning” 的地方,请在这些特定地方将 synchronized 替换为 ReentrantLock。以下是长期且频繁使用 synchronized 的示例:eYS28资讯网——每日最新资讯28at.com

synchronized(lockObj) {    frequentIO();}

我们可以将其替换为以下内容:eYS28资讯网——每日最新资讯28at.com

lock.lock();try {    frequentIO();} finally {    lock.unlock();}

参考资料:https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-E695A4C5-D335-4FA4-B886-FEB88C73F23EeYS28资讯网——每日最新资讯28at.com

最后说两句

针对虚拟线程的使用,相信大家心里已经有了答案。在对虚拟线程需要限制并发数的场景,使用信号量即可。在虚拟线程中使用线程局部变量时要注意避免缓存昂贵的可重用对象。对于使用到 synchronized 同步块或者方法的虚拟线程,建议替换为 ReentrantLock,避免影响吞吐量。eYS28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-60959-0.htmlJava 新技术:虚拟线程使用指南

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

上一篇: 接手了个项目,被if..else搞懵逼了

下一篇: 十个优秀免费开源CRM项目

标签:
  • 热门焦点
  • 线程通讯的三种方法!通俗易懂

    线程通信是指多个线程之间通过某种机制进行协调和交互,例如,线程等待和通知机制就是线程通讯的主要手段之一。 在 Java 中,线程等待和通知的实现手段有以下几种方式:Object 类下
  • Rust中的高吞吐量流处理

    作者 | Noz编译 | 王瑞平本篇文章主要介绍了Rust中流处理的概念、方法和优化。作者不仅介绍了流处理的基本概念以及Rust中常用的流处理库,还使用这些库实现了一个流处理程序
  • 一文掌握 Golang 模糊测试(Fuzz Testing)

    模糊测试(Fuzz Testing)模糊测试(Fuzz Testing)是通过向目标系统提供非预期的输入并监视异常结果来发现软件漏洞的方法。可以用来发现应用程序、操作系统和网络协议等中的漏洞或
  • 三分钟白话RocketMQ系列—— 如何发送消息

    我们知道RocketMQ主要分为消息 生产、存储(消息堆积)、消费 三大块领域。那接下来,我们白话一下,RocketMQ是如何发送消息的,揭秘消息生产全过程。注意,如果白话中不小心提到相关代
  • 每天一道面试题-CPU伪共享

    前言:了不起:又到了每天一到面试题的时候了!学弟,最近学习的怎么样啊 了不起学弟:最近学习的还不错,每天都在学习,每天都在进步! 了不起:那你最近学习的什么呢? 了不起学弟:最近在学习C
  • 零售大模型“干中学”,攀爬数字化珠峰

    文/侯煜编辑/cc来源/华尔街科技眼对于绝大多数登山爱好者而言,攀爬珠穆朗玛峰可谓终极目标。攀登珠峰的商业路线有两条,一是尼泊尔境内的南坡路线,一是中国境内的北坡路线。相
  • 拼多多APP上线本地生活入口,群雄逐鹿万亿市场

    Tech星球(微信ID:tech618)文 | 陈桥辉 Tech星球独家获悉,拼多多在其APP内上线了&ldquo;本地生活&rdquo;入口,位置较深,位于首页的&ldquo;充值中心&rdquo;内,目前主要售卖美食相关的
  • 当家的盒马,加速谋生

    来源 | 价值星球Planet作者 | 归去来自己&ldquo;当家&rdquo;的盒马,开始加速谋生了。据盒马官微消息,盒马计划今年开放生鲜供应链,将其生鲜商品送往食堂。目前,盒马在上海已经与
  • OPPO K11采用全方位护眼屏:三大护眼能力减轻视觉疲劳

    日前OPPO官方宣布,全新的OPPO K11将于7月25日正式发布,将主打旗舰影像,和同档位竞品相比,其最大的卖点就是将配备索尼IMX890主摄,堪称是2000档位影像表
Top