StructuredTaskScope - JVM 中的新并发模型

自从 Java 中虚拟线程出现以来,我们引入了一个新的、更好的并发模型,即 StructuredTaskScope。我们将在这个新的编程模型中看到一些常用的编程模式。

对于虚拟线程,由于它们非常轻量级,所以不需要将它们池化。此外,我们知道虚拟线程在阻塞任何操作时可以将其运行栈从底层平台线程 保存到堆上,并且在完成后可以将其运行栈固定到任何可用的平台线程。这是 Java 中的新功能,而且非常棒。

考虑以下代码片段:

java 复制代码
 public static Weather readFromServerA() throws InterruptedException {
        Thread.sleep(RandomUtils.nextLong(1, 100));
        return new Weather("Partly Sunny", "server-A");
    }
    public static Weather readFromServerB() throws InterruptedException {
        Thread.sleep(RandomUtils.nextLong(1, 100));
        return new Weather("Partly Sunny", "server-B");
    }
    public static Weather readFromServerC() throws InterruptedException {
        Thread.sleep(RandomUtils.nextLong(1, 100));
        return new Weather("Partly Sunny", "server-C");
    }

上面的代码返回天气信息。它模拟服务器并在 1-100 毫秒内返回信息。我们的需求是查询所有服务器并得到结果。我们只考虑第一个返回结果的请求,并立即取消其他两个请求(通过中断它们)。

让我们在 StructuredTaskScope 对象的帮助下实现这个逻辑。

java 复制代码
public static Weather readWeather() throws ExecutionException, InterruptedException, TimeoutException {
        try (var scope = new StructuredTaskScope.ShutdownOnSuccess<Weather>()){

           // need to call fork and pass runnables or callables
           scope.fork(() -> Weather.readWeatherFromServerA());
           scope.fork(() -> Weather.readWeatherFromServerB());
           scope.fork(() -> Weather.readWeatherFromServerC());

           // now we block, blocking is cheap in virtual threas
           // so no issue here
           scope.join();

           Weather weather = scope.result();
           return weather;

}

模式非常简单;我们使用了 StructuredTaskScopeShutdownOnSuccess 变体,它是由 JVM 开箱即用提供的。

我们只需一一提交三个任务,从不同的服务器获取天气即可。方法 fork() 接受可调用对象。

然后我们调用了scope. join(),为什么我们要用阻塞调用?为什么不是非阻塞调用?阻塞调用是廉价的,我们应该鼓励开发者使用阻塞调用。

最后,我们调用scope.result()方法显式获取返回结果。

现在让我们看看哪个请求得到了结果,另外两个请求中哪个被取消了。

java 复制代码
public static Weather readWeather() throws InterruptedException, ExecutionException {

        try(var scope = new StructuredTaskScope.ShutdownOnSuccess<Weather>();){

            scope.fork(() -> Weather.readWeatherFromServerA());
            scope.fork(() -> Weather.readWeatherFromServerB());
            scope.fork(() -> Weather.readWeatherFromServerC());


            Future<Weather> futureA = scope.fork(Weather::readWeatherFromServerA);
            Future<Weather> futureB = scope.fork(Weather::readWeatherFromServerB);
            Future<Weather> futureC = scope.fork(Weather::readWeatherFromServerC);

            scope.join();

            System.out.println("futureA.state() = " + futureA.state());
            System.out.println("futureB.state() = " + futureB.state());
            System.out.println("futureC.state() = " + futureC.state());

            var weather = scope.result();

           

            return weather;
        }

}

 public static void main(String[] args) throws InterruptedException, ExecutionException {
       var weather = Weather.readWeather();

    }

让我们运行这个程序几次。

java 复制代码
WARNING: Using incubator modules: jdk.incubator.concurrent
futureA.state() = FAILED
futureB.state() = FAILED
futureC.state() = SUCCESS
futureA.state() = FAILED
futureB.state() = SUCCESS
futureC.state() = FAILED

我们可以看到,每次运行不同的服务器返回了成功,而另外两个则失败了。这只是为了解释;它是技术代码;我们只需要查询不同的服务器,如下所示。

java 复制代码
public static Weather readWeather() throws ExecutionException, InterruptedException, TimeoutException {
        try (var scope = new StructuredTaskScope.ShutdownOnSuccess<Weather>()){

           // need to call fork and pass callables
           scope.fork(() -> Weather.readWeatherFromServerA());
           scope.fork(() -> Weather.readWeatherFromServerB());
           scope.fork(() -> Weather.readWeatherFromServerC());

           // now we block, blocking is cheap in virtual threas
           // so no issue here
           scope.join();

           Weather weather = scope.result();
           return weather;

}

顺便说一句,借助方法引用,上面的代码可以写得更加清晰。

java 复制代码
public static Weather readWeather() throws InterruptedException, ExecutionException {
        try(var scope = new StructuredTaskScope.ShutdownOnSuccess<Weather>();){
            scope.fork(Weather::readWeatherFromServerA);
            scope.fork(Weather::readWeatherFromServerB);
            scope.fork(Weather::readWeatherFromServerC);
            scope.join(); 
            var weather = scope.result();
            return weather;
        }
    }

哪它与 ExecutorService 有什么不同?

ExecutorService 生命周期与应用程序的生命周期一起运行。一旦应用程序启动,executor service framework 就会启动,并在应用程序终止后关闭。

但在这里,对于每个请求调用,StructuredTaskScope立即创建 而且都会在我们退出该方法后立即销毁。我们能做到这一点是因为虚拟线程很轻量级------比平台线程便宜大约 1000 倍。

其次,上面的代码可以借助完整的stage API来编写,但是如果使用这些框架,我们需要回到回调地狱和代码的层层嵌套。 😉 谁喜欢回调地狱呢?没有人!。

我们将研究更多这样的模式。这是一个全新的并发模型,我们需要确切地了解哪些模式将成为规范,但我认为这就是我们将要使用的模式。

提醒一点:它只能在启用预览功能的 Java 19 及更高版本上运行。

原文地址

相关推荐
清水33 分钟前
Spring Boot企业级开发入门
java·spring boot·后端
星释1 小时前
Rust 练习册 :Proverb与字符串处理
开发语言·后端·rust
ZZHHWW2 小时前
RocketMQ vs Kafka01 - 存储架构深度对比
后端
依_旧2 小时前
MySQL下载安装配置(超级超级入门级)
java·后端
熊小猿3 小时前
RabbitMQ死信交换机与延迟队列:原理、实现与最佳实践
开发语言·后端·ruby
淘源码d3 小时前
什么是医院随访系统?成熟在用的智慧随访系统源码
java·spring boot·后端·开源·源码·随访系统·随访系统框架
武子康3 小时前
大数据-147 Java 访问 Apache Kudu:从建表到 CRUD(含 KuduSession 刷新模式与多 Master 配置)
大数据·后端·nosql
2301_795167203 小时前
玩转Rust高级应用 如何让让运算符支持自定义类型,通过运算符重载的方式是针对自定义类型吗?
开发语言·后端·算法·安全·rust
程序猿阿越3 小时前
Kafka源码(七)事务消息
java·后端·源码阅读
ArabySide4 小时前
【Spring Boot】REST与RESTful详解,基于Spring Boot的RESTful API实现
spring boot·后端·restful