你好,Java 21

工程 | Josh Long | 2023 年 9 月 20 日 | ...

各位 Spring 爱好者,大家好!

获取安装包

在开始之前,请快速帮我一个忙。如果你还没有安装,请去安装 SKDMAN

然后运行

sdk install java 21-graalce && sdk default java 21-graalce

你就拥有了 Java 21 以及支持 Java 21 的 GraalVM,随时可以使用。在我看来,Java 21 是 Java 最重要的版本,也许是前所未有的,因为它为使用 Java 的人们带来了全新的机会。它带来了许多不错的 API 和新增功能,比如模式匹配,这些功能是多年来缓慢而稳定地添加到平台中的积累。但迄今为止最突出的特性是对虚拟线程(Project Loom)的新支持。虚拟线程和 GraalVM 本机映像意味着今天,你可以编写出性能和可伸缩性与 C、Rust 或 Go 相媲美的代码,同时保留 JVM 健壮且熟悉的生态系统。

现在是成为一名 JVM 开发者最好的时代。

我刚刚发布了一个视频,探讨了 Java 21 和 GraalVM 中的新功能和机会。

在这篇博客中,我希望重温同样的内容,并增加一些更适合文字表达的数据。

为什么选择 GraalVM 而不是传统的 Java?

首先说明。如果上面的安装过程还不清楚,我建议先安装 GraalVM。它是 OpenJDK,所以你拥有所有 OpenJDK 的组件,但它也能创建 GraalVM 本机映像。

为什么选择 GraalVM 本机映像?嗯,因为它而且超级资源高效。传统上,这个论点总是有一个反驳:“好吧,老式 Java 的 JIT 还是更快,”对此我会反驳说,“好吧,你可以在占用资源极少的情况下更容易地扩展新的实例,以弥补你可能损失的吞吐量,而且在资源消耗方面仍然具有优势!” 这确实是真的。

但现在我们甚至不必进行那种微妙的讨论了。根据 GraalVM 发布博客,Oracle GraalVM 本机映像通过配置文件引导优化,在某些基准测试中性能一直领先于 JIT,而在过去只在某些地方领先。Oracle GraalVM 与开源 GraalVM 发行版不一定相同,但重点是其最高性能层现在已超过 JRE JIT。

1*01_HtHD4jfuXOsgDMhkljQ

10MinuteMail 的这篇优秀文章介绍了他们如何使用 GraalVM 和 Spring Boot 3 将启动时间从约 30 秒缩短到约 3 毫秒,内存使用量从 6.6GB 减少到 1GB,同时保持相同的吞吐量和 CPU 利用率。太棒了。

Java 17

Java 21 中的许多功能都建立在 Java 17(在某些情况下甚至更早!)首次引入的功能之上。让我们在研究它们在 Java 21 中的最终表现之前,回顾一下其中的一些功能。

多行字符串

你知道 Java 支持多行字符串吗?这是我最喜欢的功能之一,它使得使用 JSON、JDBC、JPA QL 等变得前所未有的愉快

package bootiful.java21;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class MultilineStringTest {

    @Test
    void multiline() throws Exception {

        var shakespeare = """

                To be, or not to be, that is the question:
                Whether 'tis nobler in the mind to suffer
                The slings and arrows of outrageous fortune,
                Or to take arms against a sea of troubles
                And by opposing end them. To die—to sleep,
                No more; and by a sleep to say we end
                The heart-ache and the thousand natural shocks
                That flesh is heir to: 'tis a consummation
                Devoutly to be wish'd. To die, to sleep;
                To sleep, perchance to dream—ay, there's the rub:
                For in that sleep of death what dreams may come,
                """;
        Assertions.assertNotEquals(shakespeare.charAt(0), 'T');

        shakespeare = shakespeare.stripLeading();
        Assertions.assertEquals(shakespeare.charAt(0), 'T');
    }

}

没什么太令人惊讶的。很容易理解。三引号开始和结束多行字符串。你也可以去除前导、后导和缩进空格。

Record 类

Record 类是我最喜欢的 Java 功能之一!它们简直太棒了!你是否有这样一个类,其身份与其字段等价?当然有。想想你的基本实体、事件、DTO 等。每当你使用 Lombok 的 @Data 时,你都可以轻松地使用 record 类。它们在 Kotlin (data class) 和 Scala (case class) 中都有类似物,所以很多人也知道它们。很高兴它们终于出现在 Java 中了。

package bootiful.java21;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class RecordTest {

    record JdkReleasedEvent(String name) { }

    @Test
    void records() throws Exception {
        var event = new JdkReleasedEvent("Java21");
        Assertions.assertEquals( event.name() , "Java21");
        System.out.println(event);

    }
}

这种简洁的语法会生成一个类,包含构造函数、相应的存储、getter 方法(例如:event.name())、有效的 equals 方法以及良好的 toString() 实现。

增强型 Switch 表达式

我很少使用现有的 switch 语句,因为它笨重,而且通常有其他模式,比如 访问者模式,可以获得大部分好处。现在有了一个新的 switch,它是一个表达式,而不是语句,因此我可以将 switch 的结果赋给一个变量或返回它。

下面是将经典 switch 重构以使用新的增强型 switch 的示例

package bootiful.java21;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.time.DayOfWeek;

class EnhancedSwitchTest {

    // ①
    int calculateTimeOffClassic(DayOfWeek dayOfWeek) {
        var timeoff = 0;
        switch (dayOfWeek) {
            case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY:
                timeoff = 16;
                break;
            case SATURDAY, SUNDAY:
                timeoff = 24;
                break;
        }
        return timeoff;
    }

    // ②
    int calculateTimeOff(DayOfWeek dayOfWeek) {
        return switch (dayOfWeek) {
            case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> 16;
            case SATURDAY, SUNDAY -> 24;
        };
    }

    @Test
    void timeoff() {
        Assertions.assertEquals(calculateTimeOffClassic(DayOfWeek.SATURDAY), calculateTimeOff (DayOfWeek.SATURDAY));
        Assertions.assertEquals(calculateTimeOff(DayOfWeek.FRIDAY), 16);
        Assertions.assertEquals(calculateTimeOff(DayOfWeek.FRIDAY), 16);
    }
}
  1. 这是使用旧的、笨重的 switch 语句的经典实现
  2. 这是新的 switch 表达式

增强型 instanceof 检查

新的 instanceof 检查让我们避免了以往那种笨拙的检查并强制类型转换的方式,那种方式看起来是这样的:

var animal = (Object) new Dog ();
if (animal instanceof Dog ){
var fido  = (Dog) animal;
fido.bark();
}

并将其替换为

var animal = (Object) new Dog ();
if (animal instanceof Dog fido ){
fido.bark();
}

智能的 instanceof 会自动分配一个向下类型转换的变量,用于测试范围内。无需在同一块代码中两次指定类 Dog。智能的 instanceof 运算符用法是 Java 平台中模式匹配的首次真正尝试。模式匹配背后的思想很简单:匹配类型并从这些类型中提取数据。

密封类

从技术上讲,密封类也是 Java 17 的一部分,但它们暂时还没有带来太多好处。其基本思想是,在过去,限制类型可扩展性的唯一方法是通过可见性修饰符(publicprivate 等)。使用 sealed 关键字,你可以明确允许哪些类可以继承另一个类。这是一个巨大的进步,因为它让编译器知道哪些类型可能扩展给定类型,从而进行优化,并在编译时帮助我们判断增强型 switch 表达式中是否涵盖了所有可能的情况。让我们看看它是如何工作的。

package bootiful.java21;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class SealedTypesTest {

    // ①
    sealed interface Animal permits Bird, Cat, Dog {
    }

    // ②
    final class Cat implements Animal {
        String meow() {
            return "meow";
        }
    }

    final class Dog implements Animal {
        String bark() {
            return "woof";
        }
    }

    final class Bird implements Animal {
        String chirp() {
            return "chirp";
        }
    }

    @Test
    void doLittleTest() {
        Assertions.assertEquals(communicate(new Dog()), "woof");
        Assertions.assertEquals(communicate(new Cat()), "meow");
    }

    // ③
    String classicCommunicate(Animal animal) {
        var message = (String) null;
        if (animal instanceof Dog dog) {
            message = dog.bark();
        }
        if (animal instanceof Cat cat) {
            message = cat.meow();
        }
        if (animal instanceof Bird bird) {
            message = bird.chirp();
        }
        return message;
    }

    // ④
    String communicate(Animal animal) {
        return switch (animal) {
            case Cat cat -> cat.meow();
            case Dog dog -> dog.bark();
            case Bird bird -> bird.chirp();
        };
    }

}
  1. 我们有一个明确的密封接口,它只允许三种类型。如果在下面添加一个新的类,增强型 switch 表达式将会失败。
  2. 实现该密封接口的类必须声明为 sealed,从而声明它允许哪些类作为子类,或者必须声明为 final
  3. 我们可以使用新的 instance of 检查来更简洁地处理每种可能的类型,但在这里我们得不到编译器的帮助。
  4. 除非我们使用增强型 switch 结合模式匹配,就像我们在这里做的那样。

注意经典版本有多笨重。哎呀。我很高兴摆脱了那种写法。另一个好处是 switch 表达式现在会告诉我们是否涵盖了所有可能的情况,就像 enum 一样。谢谢,编译器!

Java 17 之后

结合所有这些功能,我们开始轻松地进入 Java 21 的世界。从现在开始,我们将探讨 Java 17 以来出现的功能。

使用 recordsswitchif 进行更高水平的模式匹配。

增强型 switch 表达式和模式匹配非常出色,这让我很好奇多年前使用 Akka 时,如果使用 Java 配上这种优秀的语法会是什么感觉。模式匹配与 Record 类结合使用时,交互体验会更好,因为 Record 类——如前所述——是其组件的概括,并且编译器了解这一点。因此,它也可以将这些组件提升为新的变量。你还可以在 if 检查中使用这种模式匹配语法。

package bootiful.java21;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.time.Instant;

class RecordsTest {

    record User(String name, long accountNumber) {
    }

    record UserDeletedEvent(User user) {
    }

    record UserCreatedEvent(String name) {
    }

    record ShutdownEvent(Instant instant) {
    }

    @Test
    void respondToEvents() throws Exception {
        Assertions.assertEquals(
                respond(new UserCreatedEvent("jlong")), "the new user with name jlong has been created"
        );
        Assertions.assertEquals(
                respond(new UserDeletedEvent(new User("jlong", 1))),
                "the user jlong has been deleted"
        );
    }

    String respond(Object o) {
        // ①
        if (o instanceof ShutdownEvent(Instant instant)) {
            System.out.println(
                "going to to shutdown the system at " + instant.toEpochMilli());
        }
        return switch (o) {
            // ②
            case UserDeletedEvent(var user) -> "the user " + user.name() + " has been deleted";
            // ③
            case UserCreatedEvent(var name) -> "the new user with name " + name + " has been created";
            default -> null;
        };
    }

}
  1. 我们有一个特殊情况,如果我们收到某个特定事件,我们希望关闭而不是生成一个 String,因此我们将结合 if 语句使用新的模式匹配支持。
  2. 在这里,我们不仅匹配类型,还提取出 UserDeletedEvent 中的 User user
  3. 在这里,我们不仅匹配类型,还提取出 UserCreatedEvent 中的 String name

所有这些特性都在早期版本的 Java 中开始生根发芽,最终在 Java 21 中达到高潮,形成了你可以称之为面向数据编程的风格。它不是面向对象编程的替代品,而是对其的补充。你可以使用模式匹配、增强型 switch 和 instanceof 运算符等功能,为你的代码赋予新的多态性,而无需在你的公共 API 中暴露分派点。

Java 21 中还有许多其他新特性。有一些小而实用的东西,当然还有 Project Loom虚拟线程。(仅虚拟线程本身就值回票价了!)让我们直接深入探讨其中一些很棒的特性。

数学改进

在人工智能和算法领域,高效的数学比以往任何时候都更重要。新的 JDK 在这方面进行了一些不错的改进,包括 BigInteger 的并行乘法以及各种除法重载,如果发生溢出则会抛出异常,而不仅仅是在发生除以零错误时。

package bootiful.java21;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.math.BigInteger;

class MathematicsTest {

    @Test
    void divisions() throws Exception {
        //<1>
        var five = Math.divideExact( 10, 2) ;
        Assertions.assertEquals( five , 5);
    }

    @Test
    void multiplication() throws Exception {
        var start = BigInteger.valueOf(10);
        // ②
        var result = start.parallelMultiply(BigInteger.TWO);
        Assertions.assertEquals(BigInteger.valueOf(10 * 2), result);
    }
}
  1. 这个第一个操作是使除法更安全、更可预测的几个重载之一
  2. 新增了对 BigInteger 实例并行乘法的支持。请记住,这只有在 BigInteger 具有数千位时才真正有用...

Future#state

如果你正在进行异步编程(是的,即使有了 Project Loom,它仍然存在),那么你会很高兴知道我们的老朋友 Future<T> 现在提供了一个 state 实例,你可以通过 switch 来查看正在进行的异步操作的状态。

package bootiful.java21;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.concurrent.Executors;

class FutureTest {

    @Test
    void futureTest() throws Exception {
        try (var executor = Executors
                .newFixedThreadPool(Runtime.getRuntime().availableProcessors())) {
            var future = executor.submit(() -> "hello, world!");
            Thread.sleep(100);
            // ①
            var result = switch (future.state()) {
                case CANCELLED, FAILED -> throw new IllegalStateException("couldn't finish the work!");
                case SUCCESS -> future.resultNow();
                default -> null;
            };
            Assertions.assertEquals(result, "hello, world!");
        }
    }
}
  1. 这会返回一个 state 对象,让我们枚举提交的 Thread 状态。它与增强型 switch 功能配合得很好。

AutoCloseable HTTP 客户端

HTTP 客户端 API 是你将来可能希望封装异步操作并使用 Project Loom 的地方。HTTP 客户端 API 自 Java 11 起就已存在,现在已经是遥远的过去整整十个版本了!但是,现在它拥有了这个漂亮的新 AutoCloseable API。

package bootiful.java21;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

class HttpTest {

    @Test
    void http () throws Exception {

        // ①
        try (var http = HttpClient
                .newHttpClient()){
            var request = HttpRequest.newBuilder(URI.create("https://httpbin.org"))
                    .GET()
                    .build() ;
            var response = http.send( request, HttpResponse.BodyHandlers.ofString());
            Assertions.assertEquals( response.statusCode() , 200);
            System.out.println(response.body());
        }
    }

}
  1. 我们希望自动关闭 HttpClient。请注意,如果你确实启动了任何线程并在其中发送 HTTP 请求,则不应使用 AutoCloseable,除非你确保只有在所有线程执行完毕之后才让其达到作用域末尾。

String 增强

在那个例子中,我使用了 HttpResponse.BodyHandlers.ofString 来获取 String 类型的响应。你可以获取各种对象,不仅仅是 String。但是 String 结果很好,因为它们是通向 Java 21 中另一个很棒功能的绝佳过渡:对处理 String 实例的新支持。这个类展示了我最喜欢的两个功能:StringBuilderrepeat 操作以及一种检测 String 中是否存在 Emoji 的方法。

package bootiful.java21;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

class StringsTest {

    @Test
    void repeat() throws Exception {
        // ①
        var line = new StringBuilder()
                .repeat("-", 10)
                .toString();
        Assertions.assertEquals("----------", line);
    }

    @Test
    void emojis() throws Exception {
        // ②
        var shockedFaceEmoji = "\uD83E\uDD2F";
        var cp = Character.codePointAt(shockedFaceEmoji.toCharArray(), 0);
        Assertions.assertTrue(Character.isEmoji(cp));
        System.out.println(shockedFaceEmoji);
    }
}
  1. 第一个例子演示了如何使用 StringBuilder 重复一个 String(我们是不是可以集体放弃我们各种各样的 StringUtils 类了?)
  2. 第二个例子演示了如何检测 String 中的 Emoji。

我同意,这些都是微小的生活质量改进,但 nonetheless 还是不错的。

序列化集合

你需要一个有序集合来对这些 String 实例进行排序。Java 提供了一些这样的集合,如 LinkedHashMapList 等,但它们没有一个共同的祖先。现在有了;欢迎 SequencedCollection!在这个例子中,我们使用了一个简单的 ArrayList<String> 并为像 LinkedHashSet 这样的集合使用了漂亮的新工厂方法。这个新工厂方法在内部进行了一些计算,以确保在你添加了构造函数中指定的那么多元素之前,它无需重新平衡(从而缓慢地重新哈希所有内容)。

package bootiful.java21;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.LinkedHashSet;
import java.util.SequencedCollection;

class SequencedCollectionTest {

    @Test
    void ordering() throws Exception {
        var list = LinkedHashSet.<String>newLinkedHashSet(100);
        if (list instanceof SequencedCollection<String> sequencedCollection) {
            sequencedCollection.add("ciao");
            sequencedCollection.add("hola");
            sequencedCollection.add("ni hao");
            sequencedCollection.add("salut");
            sequencedCollection.add("hello");
            sequencedCollection.addFirst("ola"); //<1>
            Assertions.assertEquals(sequencedCollection.getFirst(), "ola"); // ②
        }
    }
}
  1. 这会覆盖第一个元素
  2. 这会返回第一个元素

还有类似的方法用于获取最后一个元素 (getLast) 和添加最后一个元素 (addLast),甚至支持通过 reverse 方法反转集合。

虚拟线程和 Project Loom

最后,我们来谈谈 Loom。你无疑已经听过很多关于 Loom 的消息了。其基本思想是让你在大学里写的代码具备可扩展性!这是什么意思呢?让我们编写一个简单的网络服务,它可以打印出接收到的任何内容。我们必须从一个 InputStream 中读取数据,并将所有内容累积到一个新的缓冲区(一个 ByteArrayOutputStream)中。然后,当请求完成时,我们将打印 ByteArrayOutputStream 的内容。问题在于我们可能同时接收到大量数据。所以,我们将使用线程来同时处理多个请求。

这是代码

package bootiful.java21;

import java.io.ByteArrayOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executors;

class NetworkServiceApplication {

    public static void main(String[] args) throws Exception {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            try (var serverSocket = new ServerSocket(9090)) {
                while (true) {
                    var clientSocket = serverSocket.accept();
                    executor.submit(() -> {
                        try {
                            handleRequest(clientSocket);
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    });
                }
            }
        }
    }

    static void handleRequest(Socket socket) throws Exception {
        var next = -1;
        try (var baos = new ByteArrayOutputStream()) {
            try (var in = socket.getInputStream()) {
                while ((next = in.read()) != -1) {
                    baos.write(next);
                }
            }
            var inputMessage = baos.toString();
            System.out.println("request: %s".formatted(inputMessage));
        }
    }
}

这都是相当基础的网络编程入门知识。创建一个 ServerSocket,然后等待新客户端(由 Socket 实例表示)出现。每当有新客户端到来,就将其交给线程池中的一个线程处理。每个线程都从客户端 Socket 实例的 InputStream 引用中读取数据。客户端可能会断开连接、遇到延迟,或者发送大量数据,这些都是问题,因为可用的线程数量有限,我们不能把宝贵的时间浪费在它们身上。

我们使用线程来避免因处理速度不够快而导致的请求堆积。但在这里,我们又遇到了挫折,因为在 Java 21 之前,线程很昂贵!每个 Thread 大约消耗两兆字节的 RAM。所以我们将它们放入线程池中重复使用。但即便如此,如果请求过多,我们最终会陷入线程池中没有可用线程的情况。它们都卡在那里,等待某个请求完成。嗯,差不多是这样。许多线程只是坐在那里,等待 InputStream 中的下一个字节,但它们却无法使用。

线程被阻塞了。它们可能正在等待来自客户端的数据。不幸的是,服务器等待数据时,别无选择,只能坐在那里,停驻在一个线程上,不允许其他人使用它。

直到现在。Java 21 引入了一种新的线程,即虚拟线程。现在,我们可以为堆创建数百万个线程。这很容易。但从根本上说,实际情况是执行虚拟线程的物理线程仍然很昂贵。那么,JRE 如何让我们拥有数百万个用于实际工作的线程呢?它拥有一个极大地改进的运行时,现在可以检测到我们在何时阻塞并在线程上暂停执行,直到我们等待的东西到来。然后,它悄悄地将我们放回另一个线程上。实际线程充当虚拟线程的载体,允许我们启动数百万个线程。

Java 21 在所有过去会阻塞线程的地方都进行了改进,例如使用 InputStreamOutputStream 进行阻塞式 IO 以及 Thread.sleep,因此现在它们可以正确地向运行时发出信号,表示可以回收该线程并将其用于其他虚拟线程,从而即使虚拟线程“阻塞”也能让工作继续进行。你可以在这个例子中看到这一点,这个例子是我厚颜无耻地从 José Paumard 那里“偷”来的,他是 Oracle 的一位 Java 开发者布道师,我很喜欢他的工作。

package bootiful.java21;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

class LoomTest {

    @Test
    void loom() throws Exception {

        var observed = new ConcurrentSkipListSet<String>();

        var threads = IntStream
                .range(0, 100)
                .mapToObj(index -> Thread.ofVirtual() // ①
                        .unstarted(() -> {
                            var first = index == 0;
                            if (first) {
                                observed.add(Thread.currentThread().toString());
                            }
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                            if (first) {
                                observed.add(Thread.currentThread().toString());
                            }
                            try {
                                Thread.sleep(20);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                            if (first) {
                                observed.add(Thread.currentThread().toString());
                            }
                            try {
                                Thread.sleep(20);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                            if (first) {
                                observed.add(Thread.currentThread().toString());
                            }
                        }))
                .toList();

        for (var t : threads)
            t.start();

        for (var t : threads)
            t.join();

        System.out.println(observed);

        Assertions.assertTrue(observed.size() > 1);

    }

}
  1. 我们正在 Java 21 中使用新的工厂方法来创建虚拟线程。还有一个备用的工厂方法来创建 factory 方法。

这个例子启动了大量的线程,以至于造成竞争,需要共享操作系统载体线程。然后它让线程进入 sleep 状态。睡眠通常会阻塞,但在虚拟线程中不会。

我们将在每次睡眠前后对其中一个线程(启动的第一个线程)进行采样,以记录我们的虚拟线程在每次睡眠前后运行的载体线程名称。注意它们已经改变了!运行时在不同的载体线程之间移动了我们的虚拟线程,而我们的代码没有做任何改变!这就是 Project Loom 的魔力。几乎(请原谅这个双关语)没有代码更改,并且大大提高了可伸缩性(线程重用),与你否则只能通过响应式编程等方式获得的可伸缩性相当。

我们的网络服务呢?我们确实需要进行一个改变。但这只是一个基础的改变。像这样替换掉线程池:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
...
}

其他一切都保持不变,现在我们获得了无与伦比的可伸缩性!Spring Boot 应用程序通常会使用大量的 Executor 实例来处理各种事情,比如集成、消息传递、Web 服务等。如果你正在使用 2023 年 11 月即将发布的 Spring Boot 3.2 和 Java 21,那么你可以使用这个新的属性,Spring Boot 将自动为你插入虚拟线程池!太棒了。

spring.threads.virtual.enabled=true

结论

Java 21 是一个重大事件。它提供的语法可以与许多更现代的语言媲美,并且其可伸缩性与许多现代语言一样好甚至更好,而无需使用 async/await、响应式编程等方式来复杂化代码。

如果你想要本机映像,还有一个 GraalVM 项目,它为 Java 21 提供了预先(AOT)编译器。你可以使用 GraalVM 将你的高度可伸缩的 Boot 应用程序编译成 GraalVM 本机映像,这些映像几乎可以立即启动,并且占用的 RAM 是在 JVM 上运行时的一小部分。这些应用程序还受益于 Project Loom 的优势,赋予它们无与伦比的可伸缩性。

./gradlew nativeCompile

太棒了!现在我们有了一个小的二进制文件,启动速度非常快,占用的 RAM 非常少,并且具有与最具可伸缩性的运行时相当的可伸缩性。恭喜你!你是一名 Java 开发者,现在是成为一名 Java 开发者最好的时代!

订阅 Spring 通讯

订阅 Spring 通讯,保持联系

订阅

领先一步

VMware 提供培训和认证,为你的进步加速。

了解更多

获取支持

Tanzu Spring 通过一项简单的订阅即可为 OpenJDK™、Spring 和 Apache Tomcat® 提供支持和二进制文件。

了解更多

即将到来的活动

查看 Spring 社区所有即将到来的活动。

查看全部