你好,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 社群所有即將到來的活動。

檢視全部