Skip to content
<

Java 21 是鱼皮做新项目时使用的首选 LTS 版⁢本。这个版本发布了很多重要特性,其中最重要⁡的是 Virtual Threads ⁡虚拟线程

正式特性

【必备】Virtual Threads 虚拟线程

这是 Java 并发编程的革⁢命性突破,也是很多⁡ Java 开发者⁡选择 21 的理由。

什么是虚拟线程呢?

想象一下,你是一家餐厅的老板。传统的线程就像是餐厅的服务员,⁢假设每个服务员同时只能服务一桌客人。如果⁡有 1000 桌客人,你就需要 1000⁡ 个服务员,但这显然不现实。餐厅地方不够,也负担不起那么多员工的工钱。

在传统的 Java 线程模型中也是如此。如果每⁢个线程都对应操作系统的一个真实⁡线程,创建成本很高、内存占⁡用也大。当需要处理大量并发请求时,系统可能很快就会被拖垮。

举个例子,假设开 1000 个线程同时处理网络请求:

java
public void handleRequests() {  
    for (int i = 0; i < 1000; i++) {  
        new Thread(() -> {  
            // 发送网络请求,需要等待响应  
            String result = httpClient.get("https://codefather.cn");  
            System.out.println("收到响应: " + result);  
        }).start();  
    }  
}

创建 1000 个线程会消耗大量系⁢统资源(因为对应 10⁡00 个操作系统线程)⁡,而且大部分时间线程都在等待网络响应,很浪费。

而虚拟线程就像是给餐厅引入了一个智能调度系统。服务员⁢不再需要傻傻地等在客人桌边等菜上桌⁡,而是可以在等待的时候去服务其⁡他客人。当某桌的菜准备好了,系统会自动安排一个空闲的服务员去处理。

我们可以开一个虚拟线程执行器⁢执行同样的一批任务⁡,这里我用的执行器⁡会为每个任务生成一个虚拟线程来处理:

java
public void handleRequestsWithVirtualThreads() {  
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {  
        for (int i = 0; i < 1000; i++) {  
            executor.submit(() -> {  
                // 同样的网络请求代码  
                String result = httpClient.get("https://codefather.cn");  
                System.out.println("收到响应: " + result);  
            });  
        }  
    }  
}

同样是 1000 个,但是 1000 个虚拟线程只需要很少的系统资源(比如映射⁢到 8 个操作系统线程上);而且当虚拟线程等待网络响⁡应时,会让出底层的操作系统线程,操作系统线程就会自动⁡切换去执行其他虚拟线程和任务。

总结一下 Virtual Threads 的核心优势。首先是 超级轻量。一个传统线程可能需要几 MB 的内存,而一个虚拟线程只需要几 KB。你可以轻松创建百万级别的虚拟线程而不用担心系统资源。

其次是 编程简单。你不需要学习复杂的异步编程模式,跟创建一个普通线程的代码类似,一行代码就能提交异步任务。当遇到阻塞的 I/O 操作时,虚拟线程会自动让出底层的操作系统线程。

java
// 直接创建虚拟线程  
public void handleSingleUser(Long userId) {  
    Thread.ofVirtual().start(() -> {  
        // 要异步执行的任务  
        User user = userService.findById(userId);  
        processUser(user);  
    });  
}

相关面试题:什么是协程?Java 支持协程吗?

虚拟线程的实际应用

java
public class VirtualThreadWebServer {  
    public static void main(String[] args) throws IOException {  
        // 创建虚拟线程执行器  
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {  
            ServerSocket serverSocket = new ServerSocket(8080);  
            System.out.println("服务器启动在端口 8080");  
              
            while (true) {  
                Socket clientSocket = serverSocket.accept();  
                  
                // 为每个客户端连接创建虚拟线程  
                executor.submit(() -> handleClient(clientSocket));  
            }  
        }  
    }  
      
    private static void handleClient(Socket clientSocket) {  
        try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));  
             PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {  
              
            String inputLine;  
            while ((inputLine = in.readLine()) != null) {  
                // 模拟处理请求(可能涉及数据库查询等 I/O 操作)  
                Thread.sleep(100); // 虚拟线程会自动让出  
                out.println("Echo: " + inputLine);  
            }  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

虚拟线程与传统线程池的对比

java
public class ThreadComparisonExample {  
    public static void main(String[] args) throws InterruptedException {  
        int taskCount = 100000;  
          
        // 传统线程池方式  
        long start = System.currentTimeMillis();  
        try (ExecutorService executor = Executors.newFixedThreadPool(200)) {  
            CountDownLatch latch = new CountDownLatch(taskCount);  
              
            for (int i = 0; i < taskCount; i++) {  
                executor.submit(() -> {  
                    try {  
                        // 模拟 I/O 操作  
                        Thread.sleep(10);  
                    } catch (InterruptedException e) {  
                        Thread.currentThread().interrupt();  
                    } finally {  
                        latch.countDown();  
                    }  
                });  
            }  
            latch.await();  
        }  
        System.out.println("传统线程池耗时: " + (System.currentTimeMillis() - start) + "ms");  
          
        // 虚拟线程方式  
        start = System.currentTimeMillis();  
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {  
            CountDownLatch latch = new CountDownLatch(taskCount);  
              
            for (int i = 0; i < taskCount; i++) {  
                executor.submit(() -> {  
                    try {  
                        Thread.sleep(10);  
                    } catch (InterruptedException e) {  
                        Thread.currentThread().interrupt();  
                    } finally {  
                        latch.countDown();  
                    }  
                });  
            }  
            latch.await();  
        }  
        System.out.println("虚拟线程耗时: " + (System.currentTimeMillis() - start) + "ms");  
    }  
}

【必备】Switch 模式匹配

Java 14 版本推出了 Switch 表达式,能⁢够一行处理多个条件;Java 21⁡ 版本进一步优化了 Switch ⁡的能力,新增了模式匹配特性,能够更轻松地根据对象的类型做不同的处理。

没有 Switch 模式匹配⁢时,我们需要利用 ⁡instanceo⁡f 匹配类型:

java
public String processMessage(Object message) {  
    if (message instanceof String) {  
        String textMessage = (String) message;  
        return "文本消息:" + textMessage;  
    } else if (message instanceof Integer) {  
        Integer numberMessage = (Integer) message;  
        return "数字消息:" + numberMessage;  
    } else if (message instanceof List) {  
        List<?> listMessage = (List<?>) message;  
        return "列表消息,包含 " + listMessage.size() + " 个元素";  
    } else {  
        return "未知消息类型";  
    }  
}

有了模式匹配,这段代码可以变得⁢很优雅,直接在匹配对⁡象类型的同时声明了变⁡量(跟 instanceof 模式匹配有点像):

java
public String processMessage(Object message) {  
    return switch (message) {  
        case String text -> "文本消息:" + text;  
        case Integer number -> "数字消息:" + number;  
        case List<?> list -> "列表消息,包含 " + list.size() + " 个元素";  
        case null -> "空消息";  
        default -> "未知消息类型";  
    };  
}

此外,模式匹配还支持 条件判断,让处理逻辑更加精细,相当于在 case ... when ... 中写 if 条件表达式(感觉有点像 SQL 的语法)。QRpbURJSGNCpit+OOVbcJpS1SrMo+q9/tiuOML5mzhA=

java
// 根据字符串长度采用不同处理策略  
public String processText(String text) {  
    return switch (text) {  
        case String s when s.length() < 10 -> "短文本:" + s;  
        case String s when s.length() < 100 -> "中等文本:" + s.substring(0, 5);  
        case String s -> "长文本:" + s.substring(0, 10);  
    };  
}

模式匹配的高级用法

java
public class AdvancedPatternMatching {  
    // 定义一些数据类型  
    public sealed interface Expression permits Constant, BinaryOp {}  
    public record Constant(int value) implements Expression {}  
    public record BinaryOp(String operator, Expression left, Expression right) implements Expression {}  
      
    // 使用模式匹配计算表达式  
    public int evaluate(Expression expr) {  
        return switch (expr) {  
            case Constant(var value) -> value;  
            case BinaryOp("+", var left, var right) -> evaluate(left) + evaluate(right);  
            case BinaryOp("-", var left, var right) -> evaluate(left) - evaluate(right);  
            case BinaryOp("*", var left, var right) -> evaluate(left) * evaluate(right);  
            case BinaryOp("/", var left, var right) -> evaluate(left) / evaluate(right);  
            case BinaryOp(var op, var left, var right) ->   
                throw new UnsupportedOperationException("不支持的操作符: " + op);  
        };  
    }  
      
    // 处理复杂的数据结构  
    public String processData(Object data) {  
        return switch (data) {  
            case null -> "空数据";  
            case String s when s.isEmpty() -> "空字符串";  
            case String s when s.length() == 1 -> "单字符: " + s;  
            case String s -> "字符串: " + s;  
            case Integer i when i == 0 -> "零";  
            case Integer i when i > 0 -> "正整数: " + i;  
            case Integer i -> "负整数: " + i;  
            case List<?> list when list.isEmpty() -> "空列表";  
            case List<?> list when list.size() == 1 -> "单元素列表: " + list.get(0);  
            case List<?> list -> "列表,大小: " + list.size();  
            default -> "未知类型";  
        };  
    }  
}

【实用】Record 模式

Record 模式让数据的解⁢构变得更简单直观,⁡可以一次性取出 r⁡ecord 中所有需要的信息。

举个例子,先定义一些简单的 Record:

java
public record Person(String name, int age) {}  
public record Address(String city, String street) {}  
public record Employee(Person person, Address address, double salary) {}

使用 Record 模式可以⁢直接解构这些数据,⁡不用一层一层取了:

java
public String analyzeEmployee(Employee emp) {  
    return switch (emp) {  
        // 一次性提取所有需要的信息  
        case Employee(Person(var name, var age), Address(var city, var street), var salary)   
            when salary > 50000 ->   
            String.format("%s(%d岁)是高薪员工,住在%s%s,月薪%.0f",   
                         name, age, city, street, salary);  
        case Employee(Person(var name, var age), var address, var salary) ->   
            String.format("%s(%d岁)月薪%.0f,住在%s",   
                         name, age, salary, address.city());  
    };  
}

这种写法适合追求极致简洁代码的程序员,可以在一行代码中同时完成 类型检查数据提取条件判断

Record 模式的实际应用

java
public class RecordPatternExample {  
    // 定义 HTTP 响应的数据结构  
    public sealed interface HttpResponse permits Success, Error, Redirect {}  
    public record Success(int code, String body, Map<String, String> headers) implements HttpResponse {}  
    public record Error(int code, String message) implements HttpResponse {}  
    public record Redirect(int code, String location) implements HttpResponse {}  
      
    // 处理 HTTP 响应  
    public void handleResponse(HttpResponse response) {  
        switch (response) {  
            case Success(var code, var body, var headers) when code == 200 -> {  
                System.out.println("成功响应: " + body);  
                if (headers.containsKey("Content-Type")) {  
                    System.out.println("内容类型: " + headers.get("Content-Type"));  
                }  
            }  
            case Success(var code, var body, var headers) -> {  
                System.out.println("成功响应 " + code + ": " + body);  
            }  
            case Error(var code, var message) when code >= 500 -> {  
                System.err.println("服务器错误 " + code + ": " + message);  
            }  
            case Error(var code, var message) -> {  
                System.err.println("客户端错误 " + code + ": " + message);  
            }  
            case Redirect(var code, var location) -> {  
                System.out.println("重定向 " + code + " -> " + location);  
            }  
        }  
    }  
      
    // 解析嵌套的数据结构  
    public void processOrder(Object order) {  
        record Item(String name, double price, int quantity) {}  
        record Customer(String name, String email) {}  
        record Order(Customer customer, List<Item> items, double total) {}  
          
        switch (order) {  
            case Order(Customer(var customerName, var email), var items, var total)   
                when total > 1000 -> {  
                System.out.println("大订单:客户 " + customerName + " (" + email + ")");  
                System.out.println("订单总额:" + total);  
                items.forEach(item -> System.out.println("- " + item.name() + " x" + item.quantity()));  
            }  
            case Order(Customer(var customerName, var email), var items, var total) -> {  
                System.out.println("普通订单:客户 " + customerName);  
                System.out.println("商品数量:" + items.size() + ",总额:" + total);  
            }  
            default -> System.out.println("无效订单");  
        }  
    }  
}

【了解】有序集合

Java 21 的有序集合为⁢我们提供了更直观的⁡方式来操作集合的头⁡尾元素,说白了就是补了几个方法:

java
List<String> tasks = new ArrayList<>();  
tasks.addFirst("鱼皮的任务");    // 添加到开头  
tasks.addLast("小阿巴的任务");   // 添加到结尾  
  
String firstStr = tasks.getFirst();  // 获取第一个  
String lastStr = tasks.getLast();   // 获取最后一个  
  
String removedFirst = tasks.removeFirst();  // 删除并返回第一个  
String removedLast = tasks.removeLast();    // 删除并返回最后一个  
  
List<String> reversed = tasks.reversed();   // 反转列表

除了 List 之外,SequencedMap 接口(比如⁢ LinkedHashMap)和 Se⁡quencedSet 接口(比如 Li⁡nkedHashSet)也新增了类似的方法。本质上都是实现了有序集合接口:

有序集合的使用示例

java
public class SequencedCollectionExample {  
    public static void main(String[] args) {  
        // List 的有序操作  
        List<String> list = new ArrayList<>();  
        list.addFirst("开始");  
        list.addLast("结束");  
        list.addFirst("真正的开始");  
          
        System.out.println("列表: " + list); // [真正的开始, 开始, 结束]  
        System.out.println("第一个: " + list.getFirst());  
        System.out.println("最后一个: " + list.getLast());  
          
        // Set 的有序操作  
        SequencedSet<Integer> set = new LinkedHashSet<>();  
        set.addFirst(1);  
        set.addLast(3);  
        set.addFirst(0);  
        set.addLast(4);  
          
        System.out.println("有序集合: " + set); // [0, 1, 3, 4]  
        System.out.println("反转后: " + set.reversed()); // [4, 3, 1, 0]  
          
        // Map 的有序操作  
        SequencedMap<String, String> map = new LinkedHashMap<>();  
        map.putFirst("first", "第一个");  
        map.putLast("last", "最后一个");  
        map.putFirst("zero", "第零个");  
          
        System.out.println("有序映射: " + map);  
        System.out.println("第一个键值对: " + map.firstEntry());  
        System.out.println("最后一个键值对: " + map.lastEntry());  
    }  
}

【了解】分代 ZGC

Java 21 中的分代 ZGC 可以说是⁢垃圾收集器领域的一个重大⁡突破。ZGC 从 Java 11⁡ 开始就以其超低延迟而闻名,但是它并没有采用分代的设计思路。

在这之前,ZGC 对所有对象一视同仁,无论是刚创建的新对象还是存活⁢了很久的老对象,都使用同样的收集策略。这虽然⁡保证了一致的低延迟,但在内存分配密集的应用中⁡,效率并不是最优的

分代 ZGC 的核心思想是基于一个现象 —— 大部分对象都是 "朝生夕死" 的。它⁢将堆内存划分为年轻代和老年代两个区域,年轻代的垃圾收集⁡可以更加频繁和高效,因为大部分年轻对象很快就会死亡,收⁡集器可以快速清理掉这些垃圾;而老年代的收集频率相对较低,减少了对长期存活对象的不必要扫描。

分代 ZGC 的使用

bash
# 启用分代 ZGC  
java -XX:+UseZGC -XX:+UnlockExperimentalVMOptions -XX:+UseGenerationalZGC MyApp  
  
# 监控 GC 性能  
java -XX:+UseZGC -XX:+UnlockExperimentalVMOptions -XX:+UseGenerationalZGC \  
     -Xlog:gc:gc.log MyApp  
  
# 调整年轻代大小(可选)  
java -XX:+UseZGC -XX:+UnlockExperimentalVMOptions -XX:+UseGenerationalZGC \  
     -XX:NewRatio=2 MyApp

分代 ZGC 的优势:

  • 更低的分配开销:年轻代分配更高效

  • 更少的 GC 工作:大部分对象在年轻代就被回收

  • 更好的缓存局部性:年轻对象聚集在一起

  • 保持低延迟:继承了 ZGC 的低延迟特性

【了解】其他正式特性

弃用 Windows 32 位 x86 端口

Java 21 将 Wind⁢ows 32 位支⁡持标记为弃用:

bash
# 32 位 Windows 系统将不再被官方支持  
# 建议迁移到 64 位系统

准备禁止代理的动态加载

Java 21 为禁止运行时⁢动态加载 Java⁡ 代理做准备:

bash
# 未来版本将不允许运行时加载代理  
# java -javaagent:agent.jar MyApp  # 启动时加载仍然支持  
  
# 如果需要运行时加载(不推荐)  
java -XX:+EnableDynamicAgentLoading MyApp

密钥封装机制 API

Java 21 引入了密钥封装机制(KEM)API:

java
import javax.crypto.KEM;  
import javax.crypto.KeyGenerator;  
import java.security.KeyPair;  
import java.security.KeyPairGenerator;  
  
public class KEMExample {  
    public static void main(String[] args) throws Exception {  
        // 生成密钥对  
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");  
        kpg.initialize(2048);  
        KeyPair keyPair = kpg.generateKeyPair();  
          
        // 创建 KEM 实例  
        KEM kem = KEM.getInstance("RSA-KEM");  
          
        // 发送方:封装密钥  
        KEM.Encapsulator encapsulator = kem.newEncapsulator(keyPair.getPublic());  
        KEM.Encapsulated encapsulated = encapsulator.encapsulate();  
          
        byte[] encapsulatedKey = encapsulated.encapsulation();  
        byte[] sharedSecret = encapsulated.key();  
          
        // 接收方:解封装密钥  
        KEM.Decapsulator decapsulator = kem.newDecapsulator(keyPair.getPrivate());  
        byte[] recoveredSecret = decapsulator.decapsulate(encapsulatedKey);  
          
        // 验证密钥一致性  
        System.out.println("密钥封装成功: " +   
                          java.util.Arrays.equals(sharedSecret, recoveredSecret));  
    }  
}

预览特性

【了解】字符串模板(预览)

Java 21 引入了字符串模板作为预览特性:

java
// 需要启用预览功能  
// javac --enable-preview --release 21 StringTemplateExample.java  
// java --enable-preview StringTemplateExample  
  
public class StringTemplateExample {  
    public static void main(String[] args) {  
        String name = "张三";  
        int age = 25;  
        double salary = 15000.50;  
          
        // 字符串模板  
        String message = STR."员工 \{name} 年龄 \{age},月薪 \{salary}";  
        System.out.println(message);  
          
        // 格式化模板  
        String formatted = FMT."员工 %-10s\{name} 年龄 %3d\{age},月薪 %8.2f\{salary}";  
        System.out.println(formatted);  
          
        // 原始模板  
        StringTemplate st = RAW."员工 \{name} 年龄 \{age},月薪 \{salary}";  
        System.out.println("模板: " + st.template());  
        System.out.println("值: " + st.values());  
    }  
}

【了解】未命名模式和变量(预览)

Java 21 引入了未命名模式和变量:

java
// 未命名变量  
for (int i = 0, _ = sideEffect(); i < 10; i++) {  
    // _ 表示不关心的变量  
}  
  
// 在 switch 中使用未命名模式  
switch (obj) {  
    case Point(var x, _) -> System.out.println("X坐标: " + x);  
    case String _ -> System.out.println("某个字符串");  
    default -> System.out.println("其他");  
}  
  
// 在 catch 中使用  
try {  
    riskyOperation();  
} catch (IOException _) {  
    // 不关心异常对象  
    System.out.println("IO 异常发生");  
}

【了解】未命名类和实例主方法(预览)

Java 21 简化了简单程序的编写:

java
// 传统写法  
public class HelloWorld {  
    public static void main(String[] args) {  
        System.out.println("Hello World");  
    }  
}  
  
// Java 21 简化写法(预览)  
void main() {  
    System.out.println("Hello World");  
}  
  
// 或者  
public static void main() {  
    System.out.println("Hello World");  
}

孵化器特性

【了解】向量 API(第六次孵化器)

Java 21 继续完善向量 API:

java
import jdk.incubator.vector.*;  
  
public class VectorExample {  
    private static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;  
      
    // 向量化的矩阵乘法  
    public static void matrixMultiply(float[][] a, float[][] b, float[][] result) {  
        int n = a.length;  
        int m = b[0].length;  
        int p = a[0].length;  
          
        for (int i = 0; i < n; i++) {  
            for (int j = 0; j < m; j += SPECIES.length()) {  
                FloatVector sum = FloatVector.zero(SPECIES);  
                  
                for (int k = 0; k < p; k++) {  
                    FloatVector aVec = FloatVector.broadcast(SPECIES, a[i][k]);  
                    FloatVector bVec = FloatVector.fromArray(SPECIES, b[k], j);  
                    sum = aVec.fma(bVec, sum);  
                }  
                  
                sum.intoArray(result[i], j);  
            }  
        }  
    }  
      
    public static void main(String[] args) {  
        float[][] a = {{1, 2}, {3, 4}};  
        float[][] b = {{5, 6}, {7, 8}};  
        float[][] result = new float[2][2];  
          
        matrixMultiply(a, b, result);  
          
        System.out.println("矩阵乘法结果:");  
        for (float[] row : result) {  
            System.out.println(Arrays.toString(row));  
        }  
    }  
}

总结

Java 21 是一个里程碑式的版本,虚拟线程的正式化彻底改⁢变了 Java 的并发编程模式,让开发者⁡可以用简单的同步代码编写高并发应用。Sw⁡itch 模式匹配和 Record 模式的正式化让数据处理更加优雅和类型安全。

分代 ZGC 的引入为低延迟应用提供了更好⁢的性能,有序集合接口让集合操⁡作更加直观。作为 LTS 版⁡本,Java 21 在稳定性和新特性之间找到了很好的平衡。

Java 21 已经成为现代 Java 开⁢发的首选版本,其革命性的虚拟⁡线程特性和完善的模式匹配支持⁡,使其成为从 Java 8/11/17 升级的理想选择。