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

原文地址

相关推荐
dunky4 分钟前
Spring 的三级缓存与循环依赖
后端·spring
子兮曰6 分钟前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端
Chenyiax22 分钟前
从 PyTorch Attention 源码理解 KV Cache、缓存命中与 Prefix Cache
后端
IT_陈寒44 分钟前
React状态更新总是不及时?你可能漏了这步批处理机制
前端·人工智能·后端
Jinkey2 小时前
要用户手机号真的是为了打骚扰电话吗?浅谈微信生态会员账号体系与资产合并
后端·微信·微信小程序
葫芦和十三2 小时前
图解 MongoDB 06|模式演进:无 schema 是优势还是债
后端·mongodb·agent
葫芦和十三9 小时前
图解 MongoDB 05|文档模型设计:内嵌 vs 引用,反范式不是免费午餐
后端·mongodb·agent
不能放弃治疗13 小时前
单 Agent 实现模式
后端
IT_陈寒15 小时前
Redis内存爆了,原来我漏掉了这个致命配置
前端·人工智能·后端
fliter16 小时前
最后一块拼图:用 bitvec 构造 IPv4 包,真正做出自己的 Ping
后端