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 及更高版本上运行。

原文地址

相关推荐
9523621 分钟前
SpringBoot统一功能处理
java·spring boot·后端
rleS IONS42 分钟前
SpringBoot中自定义Starter
java·spring boot·后端
DevilSeagull1 小时前
MySQL(2) 客户端工具和建库
开发语言·数据库·后端·mysql·服务
TeDi TIVE2 小时前
springboot和springframework版本依赖关系
java·spring boot·后端
雨辰AI2 小时前
SpringBoot3 + 人大金仓 V9 微服务监控实战|Prometheus+Grafana+SkyWalking 全链路监控
数据库·后端·微服务·grafana·prometheus·skywalking
Nicander3 小时前
理解 mybatis 源码:vibe-coding一个mini-mybatis
后端·mybatis
小呆呆6663 小时前
Codex 穷鬼大救星
前端·人工智能·后端
FelixBitSoul4 小时前
缓存淘汰策略全解:从原理到手写实现(Java / Go / Python)
后端·面试
AI人工智能+电脑小能手4 小时前
【大白话说Java面试题】【Java基础篇】第29题:静态代理和动态代理的区别是什么
java·开发语言·后端·面试·代理模式
dovens5 小时前
SpringBoot集成MQTT客户端
java·spring boot·后端