您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Java 21,虚拟线程、结构化并发和作用域值

时间:2023-09-26 14:22:31  来源:微信公众号  作者:小技术君

如果你仍然认为之前的JDK 17没有太多改变,那么JDK 21需要引起你的注意。因为JDK 21引入了一种新型的并发编程模型。

目前在JAVA中的多线程并发编程是我们头痛的另一部分。感觉学起来很困难,使用起来也很复杂。但是回头看看使用其他语言的朋友,他们根本没有这种麻烦,比如GoLang,使用起来非常顺畅。

JDK 21在这个领域取得了巨大的改进,使Java并发编程变得更加简单和顺畅。更准确地说,这些改进在JDK 19或JDK 20中已经存在。

1*wtYzgJzJD8rGtysVoIW1cw.png

其中,虚拟线程、作用域值和结构化并发是多线程并发编程的一些功能。

一、虚拟线程

虚拟线程是基于协程的线程,类似于其他语言中的协程,但也有一些区别。

虚拟线程附加在主线程上。如果主线程被销毁,虚拟线程将不再存在。

相似之处:

  • 虚拟线程和协程都很轻量级,它们的创建和销毁开销比传统操作系统线程要小。
  • 虚拟线程和协程都可以通过挂起和恢复来在线程之间切换,从而避免了线程上下文切换的开销。
  • 虚拟线程和协程都可以以异步和非阻塞的方式处理任务,提高了应用程序的性能和响应能力。

不同之处:

  • 虚拟线程是在JVM级别实现的,而协程是在语言级别实现的。因此,虚拟线程的实现可以与支持JVM的任何语言一起使用,而协程的实现需要特定的编程语言支持。
  • 虚拟线程是协程的基于线程的实现,因此它们可以使用与线程相关的API,如ThreadLocal、Lock和Semaphore。协程不依赖于线程,通常需要特定的异步编程框架和API。
  • 虚拟线程的调度由JVM管理,而协程的调度由编程语言或异步编程框架管理。因此,虚拟线程可以更好地与其他线程合作,而协程更适合处理异步任务。

总的来说,虚拟线程是一种新的线程类型,可以提高应用程序的性能和资源利用率,同时还可以使用传统的与线程相关的API。虚拟线程与协程有许多相似之处,但也存在一些不同之处。

虚拟线程确实可以使多线程编程更加简单和高效。与传统的操作系统线程相比,创建和销毁虚拟线程的开销更小,线程上下文切换的开销也更小,因此可以大大减少多线程编程中的资源消耗和性能瓶颈。

使用虚拟线程,开发人员可以像编写传统线程代码一样编写代码,而不必担心线程的数量和调度,因为JVM会自动管理虚拟线程的数量和调度。此外,虚拟线程还支持传统的与线程相关的API,如ThreadLocal、Lock和Semaphore,这使得开发人员更容易将传统线程代码迁移到虚拟线程中。

虚拟线程的引入使多线程编程更加高效、简单和安全,允许开发人员更多关注业务逻辑,而不必过多关注底层线程管理。

二、结构化并发

结构化并发是一种旨在通过提供结构化且易于遵循的方法来简化并发编程的编程范例。使用结构化并发,开发人员可以创建更容易理解和调试、不容易出现竞态条件和其他与并发相关的错误的并发代码。在结构化并发中,所有并发代码都被结构化为称为任务的明确定义的工作单元。任务以结构化的方式创建、执行和完成,任务的执行始终保证在其父任务完成之前完成。

结构化并发可以使多线程编程更加简单和可靠。在传统的多线程编程中,线程的启动、执行和终止都是由开发人员手动管理的,因此容易出现线程泄漏、死锁和不正确的异常处理等问题。

使用结构化并发,开发人员可以更自然地组织并发任务,使任务之间的依赖关系更清晰,代码逻辑更简洁。结构化并发还提供了一些异常处理机制,以更好地管理并发任务中的异常,避免由异常引起的程序崩溃或数据不一致。

此外,结构化并发还可以通过限制并发任务的数量和优先级来防止资源

竞争和饥饿现象。这些特性使得开发人员能够更容易地实现高效且可靠的并发程序,而不必过多关注底层线程管理。

三、作用域值

作用域值是JDK 20中的一项功能,允许开发人员创建仅限于特定线程或任务的作用域值。作用域值类似于线程本地变量,但设计用于与虚拟线程和结构化并发一起使用。它们允许开发人员以结构化的方式在不同部分的应用程序之间传递上下文信息,例如用户身份验证或请求特定数据。

四、实践

在继续以下探索之前,您需要至少下载JDK 19或直接下载JDK 20。截止到2023年9月,JDK 20是官方发布的最高版本。如果使用JDK 19,您将无法体验到Scoped Values功能。

1*GQ22_fxZ-eRKk85BBXuHWQ.png

或者直接下载JDK 21的早期访问版本。

1*0hHWnZaMVfsKlVCLjMJUSg.png

如果您使用的是IDEA,则您的IDEA版本必须至少为2022.3或更高版本,否则不支持这样的新JDK版本。

如果您使用的是JDK 19或JDK 20,您应该在项目设置中将语言级别设置为19或20。否则,在编译时会提示您无法使用预览版本功能。虚拟线程是预览版本的功能。

1*6oGVASOHa2kRTtbZ--F7AQ.png

如果您使用的是JDK 21,请将语言级别设置为X - 实验性功能。此外,由于JDK 21不是官方版本,您需要进入IDEA设置(请注意,这是IDEA设置,而不是项目设置),并手动将项目的目标字节码版本更改为21。当前,最高选项为20,即JDK 20。将其设置为21后,您可以在JDK 21中使用这些功能。

1*8ltpmMzUzE4u5CAGgNvIOg.png

1. 虚拟线程

现在我们如何启动线程?

首先,声明一个线程类,实现Runnable接口,并实现run方法。

public class SimpleThread implements Runnable {

    @Override
    public void run() {
        System.out.println("name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

然后,您可以使用这个线程类并启动线程。

Thread thread = new Thread(new SimpleThread());
thread.start();

拥有虚拟线程后,如何实现呢?

Thread.ofPlatform().name("thread-test").start(new SimpleThread());

以下是使用虚拟线程的几种方式。

(1) 直接启动虚拟线程

Thread thread = Thread.startVirtualThread(new SimpleThread());

(2) 使用ofVirtual(),构建器模式启动虚拟线程,您可以设置线程名称、优先级、异常处理和其他配置

Thread.ofVirtual()
      .name("thread-test")
      .start(new SimpleThread());

或者:

Thread thread = Thread.ofVirtual()
      .name("thread-test")
      .uncaughtExceptionHandler((t, e) -> {
          System.out.println(t.getName() + e.getMessage());
      })
      .unstarted(new SimpleThread());
thread.start();

(3) 使用工厂创建线程

ThreadFactory factory = Thread.ofVirtual().factory();
Thread thread = factory.newThread(new SimpleThread());
thread.setName("thread-test");
thread.start();

(4) 使用Executors

ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
Future<?> submit = executorService.submit(new SimpleThread());
Object o = submit.get();

2. 结构化并发

想象以下情景。假设您有三个任务需要同时执行。只要任何一个任务完成并返回结果,就可以直接使用该结果,可以停止其他两个任务。例如,一个天气服务通过三个渠道获取天气情况,只要一个渠道返回即可。

在这种情况下,在Java 8下应该做什么,当然也是可以的。

List<Future<?>> futures = executor.invokeAll(tasks);
String result = executor.invokeAny(tasks);

使用ExecutorService的invokeAll和invokeAny方法实现,但会有一些额外的工作。在获取第一个结果后,您需要手动关闭另一个线程。

在JDK 21中,可以使用结构化编程来实现。

ShutdownOnSuccess捕获第一个结果并关闭任务范围以中断未完成的线程并唤醒调用线程。

一种情况是任何子任务的结果都可以直接使用,而无需等待其他未完成任务的结果。

它定义了获取第一个结果或在所有子任务失败时抛出异常的方法。

public static void mAIn(String[] args) throws IOException {
    try (var scope = new StructuredTaskScope.ShutdownOnSuccess()) {
        Future<String> res1 = scope.fork(() -> runTask(1));
        Future<String> res2 = scope.fork(() -> runTask(2));
        Future<String> res3 = scope.fork(() -> runTask(3));
        scope.join();
        System.out.println("scope:" + scope.result());
    } catch (ExecutionException | InterruptedException e) {
        throw new RuntimeException(e);
    }
}

public static String runTask(int i) throws InterruptedException {
    Thread.sleep(

1000);
    long l = new Random().nextLong();
    String s = String.valueOf(l);
    System.out.println(i + "task:" + s);
    return s;
}

ShutdownOnFailure

执行多个任务,只要有一个失败(发生异常或引发其他活动异常),就停止其他未完成的任务,并使用scope.throwIfFailed来捕获并抛出异常。

如果所有任务都正常,可以使用Feture.get()或*Feture.resultNow()来获取结果。

public static void main(String[] args) throws IOException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Future<String> res1 = scope.fork(() -> runTaskWithException(1));
        Future<String> res2 = scope.fork(() -> runTaskWithException(2));
        Future<String> res3 = scope.fork(() -> runTaskWithException(3));
        scope.join();
        scope.throwIfFailed(Exception::new);

        String s = res1.resultNow();
        System.out.println(s);

        String result = Stream.of(res1, res2, res3)
                             .map(Future::resultNow)
                             .collect(Collectors.joining());
        System.out.println("result:" + result);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static String runTaskWithException(int i) throws InterruptedException {
    Thread.sleep(1000);
    long l = new Random().nextLong(3);
    if (l == 0) {
        throw new InterruptedException();
    }
    String s = String.valueOf(l);
    System.out.println(i + "task:" + s);
    return s;
}

3. 作用域值

我们一定使用过ThreadLocal,它是线程本地变量,只要线程不销毁,就可以随时获取ThreadLocal中的变量值。作用域值也可以在线程内的任何时候获取变量,但它有一个作用域的概念,当超出作用域时将被销毁。

public class ScopedValueExample {
    final static ScopedValue<String> LoginUser = ScopedValue.newInstance();

    public static void main(String[] args) throws InterruptedException {
        ScopedValue.where(LoginUser, "Tom")
                   .run(() -> {
                       new Service().login();
                   });

        Thread.sleep(2000);
    }

    static class Service {
        void login() {
            System.out.println("user:" + LoginUser.get());
        }
    }
}

上面的示例模拟了用户登录过程,使用ScopedValue.newInstance()声明了一个ScopedValue,使用ScopedValue.where为ScopedValue设置了一个值,并使用run方法执行接下来要做的事情,以便在run()内部随时获取ScopedValue。在run方法中模拟了service的登录方法,不需要传递参数LoginUser,直接通过LoginUser.get方法可以直接获取当前登录用户的值。



Tags:Java 21   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Java 21 神仙特性:虚拟线程使用指南
虚拟线程是由 Java 21 版本中实现的一种轻量级线程。它由 JVM 进行创建以及管理。虚拟线程和传统线程(我们称之为平台线程)之间的主要区别在于,我们可以轻松地在一个 Java 程序...【详细内容】
2023-12-28  Search: Java 21  点击:(107)  评论:(0)  加入收藏
别再乱用了,Java 21 将弃用、删除这些功能!
尽管Java 是我使用过的向后兼容程度最高的语言和环境之一,但始终存在功能弃用甚至删除的可能性。Java 21 将弃用两个功能,这就是我们今天要讨论的内容。1 为什么要弃用功能?弃...【详细内容】
2023-12-25  Search: Java 21  点击:(137)  评论:(0)  加入收藏
Java 21 的虚拟线程:高性能并发应用的福音
Java 21 最重要的特性之一就是虚拟线程 (JEP 444)。这些轻量级的线程降低了编写、维护和观察高吞吐量并行应用所需的努力。在讨论新特性之前,让我们先看一下当前的状态,以便更...【详细内容】
2023-12-08  Search: Java 21  点击:(247)  评论:(0)  加入收藏
Java 11 到 Java 21:无缝迁移的可视化指南
译者 | 卯金雍审校 | 重楼迁移到 Java 21 的理由在我们探索从 Java 11 迁移到 Java 21 的必要性的旅程中,我们深入研究了四个关键类别,并强调了这一转变的重要性。每个方面都...【详细内容】
2023-12-08  Search: Java 21  点击:(225)  评论:(0)  加入收藏
Spring Boot宣布支持Java 21:开发者的福音与挑战
近期的更新中,SpringBoot宣布已经支持Java21,这是一个令开发者们兴奋的消息。Java21是最新的Java版本,带来了许多新的功能和改进,使得开发过程更加高效和方便。SpringBoot作为一...【详细内容】
2023-10-08  Search: Java 21  点击:(365)  评论:(0)  加入收藏
Java 21:下一个LTS版本,提供了虚拟线程、记录模式和模式匹配
作者 | Michael Redlich译者 | 平川策划 | 丁晓昀Oracle 发布 Java 编程语言和虚拟机的第 21 个版本。这是自 2021 年 JDK 17 发布以来的第一个长期支持(LTS)版本。最终的特性...【详细内容】
2023-09-28  Search: Java 21  点击:(257)  评论:(0)  加入收藏
Java 21,虚拟线程、结构化并发和作用域值
如果你仍然认为之前的JDK 17没有太多改变,那么JDK 21需要引起你的注意。因为JDK 21引入了一种新型的并发编程模型。目前在Java中的多线程并发编程是我们头痛的另一部分。感觉...【详细内容】
2023-09-26  Search: Java 21  点击:(343)  评论:(0)  加入收藏
Java 21 将不再有public static void main !
之前的Java是这样: Java 21会是这样: 看到这个消息,我最大的感受是:卧槽,怎么就Java 21了?!我还在用Java 8 呢!实际上,从Java 8 到Java 21,中间也就是Java 11, Java 17是长期支持版本,...【详细内容】
2023-06-06  Search: Java 21  点击:(179)  评论:(0)  加入收藏
▌简易百科推荐
Java 8 内存管理原理解析及内存故障排查实践
本文介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,以及各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时...【详细内容】
2024-03-20  vivo互联网技术    Tags:Java 8   点击:(14)  评论:(0)  加入收藏
如何编写高性能的Java代码
作者 | 波哥审校 | 重楼在当今软件开发领域,编写高性能的Java代码是至关重要的。Java作为一种流行的编程语言,拥有强大的生态系统和丰富的工具链,但是要写出性能优异的Java代码...【详细内容】
2024-03-20    51CTO  Tags:Java代码   点击:(21)  评论:(0)  加入收藏
在Java应用程序中释放峰值性能:配置文件引导优化(PGO)概述
译者 | 李睿审校 | 重楼在Java开发领域,优化应用程序的性能是开发人员的持续追求。配置文件引导优化(Profile-Guided Optimization,PGO)是一种功能强大的技术,能够显著地提高Ja...【详细内容】
2024-03-18    51CTO  Tags:Java   点击:(24)  评论:(0)  加入收藏
Java生产环境下性能监控与调优详解
堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和 Survivor 区,...【详细内容】
2024-02-04  大雷家吃饭    Tags:Java   点击:(56)  评论:(0)  加入收藏
在项目中如何避免和解决Java内存泄漏问题
在Java中,内存泄漏通常指的是程序中存在一些不再使用的对象或数据结构仍然保持对内存的引用,从而导致这些对象无法被垃圾回收器回收,最终导致内存占用不断增加,进而影响程序的性...【详细内容】
2024-02-01  编程技术汇  今日头条  Tags:Java   点击:(68)  评论:(0)  加入收藏
Java中的缓存技术及其使用场景
Java中的缓存技术是一种优化手段,用于提高应用程序的性能和响应速度。缓存技术通过将计算结果或者经常访问的数据存储在快速访问的存储介质中,以便下次需要时可以更快地获取。...【详细内容】
2024-01-30  编程技术汇    Tags:Java   点击:(72)  评论:(0)  加入收藏
JDK17 与 JDK11 特性差异浅谈
从 JDK11 到 JDK17 ,Java 的发展经历了一系列重要的里程碑。其中最重要的是 JDK17 的发布,这是一个长期支持(LTS)版本,它将获得长期的更新和支持,有助于保持程序的稳定性和可靠性...【详细内容】
2024-01-26  政采云技术  51CTO  Tags:JDK17   点击:(88)  评论:(0)  加入收藏
Java并发编程高阶技术
随着计算机硬件的发展,多核处理器的普及和内存容量的增加,利用多线程实现异步并发成为提升程序性能的重要途径。在Java中,多线程的使用能够更好地发挥硬件资源,提高程序的响应...【详细内容】
2024-01-19  大雷家吃饭    Tags:Java   点击:(105)  评论:(0)  加入收藏
这篇文章彻底让你了解Java与RPA
前段时间更新系统的时候,发现多了一个名为Power Automate的应用,打开了解后发现是一个自动化应用,根据其描述,可以自动执行所有日常任务,说的还是比较夸张,简单用了下,对于office、...【详细内容】
2024-01-17  Java技术指北  微信公众号  Tags:Java   点击:(95)  评论:(0)  加入收藏
Java 在 2023 年仍然流行的 25 个原因
译者 | 刘汪洋审校 | 重楼学习 Java 的过程中,我意识到在 90 年代末 OOP 正值鼎盛时期,Java 作为能够真正实现这些概念的语言显得尤为突出(尽管我此前学过 C++,但相比 Java 影响...【详细内容】
2024-01-10  刘汪洋  51CTO  Tags:Java   点击:(74)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条