Java-21新功能使用介绍-虚拟线程+有序集合

介绍

Oracle JDK 下载:https://www.oracle.com/cn/java/technologies/downloads

OpenJDK 21 下载:https://jdk.java.net/21/

Java 21作为继JDK17之后的LTS版本,

JEP 内容 分类
JEP 444 虚拟线程 核心 Java 库
JEP 431 有序集合 核心 Java 库
JEP 442 外部函数和内存 API (第三次预览) 核心 Java 库
JEP 446 作用域值 (预览) 核心 Java 库
JEP 448 Vector API (第六次孵化) 核心 Java 库
JEP 453 结构化并发 (预览) 核心 Java 库
JEP 440 Record 模式 Java 语言规范
JEP 441 switch 模式匹配 Java 语言规范
JEP 430 字符串模板 (预览) Java 语言规范
JEP 443 未命名模式的变量 (预览) Java 语言规范
JEP 445 未命名类和 main 方法 (预览) Java 语言规范
JEP 439 分代 ZGC HotSpot
JEP 449 弃用 Windows 32 位 x86 端口 HotSpot
JEP 451 准备禁止动态加载代理 HotSpot
JEP 452 密钥封装机制 API 安全库

虚拟线程

虚拟线程是 Java 21 中最为重要的特性。Java 从 Java 19 开始引入虚拟线程,在 Java 21 中就正式升级为正式特性。虚拟线程是轻量级的线程,可以在显著的减少代码编写的同时提高系统的吞吐量(注意,虚拟线程不适用于 CPU 计算密集型任务)

引入虚拟线程原因

一直以来,在 Java 并发编程中,Thread 都是十分重要的一部分,Thread 是 Java 中的并发单元,每个 Thread 线程都提供了一个堆栈来存储局部变量和方法调用,以及线程上下文等相关信息。但问题是线程和进程一样,都是一项昂贵的资源,JDK 将 Thread 线程实现为操作系统线程的包装器,成本很高,而且数量有限。因此我们会使用线程池来管理线程,同时限制线程的数量。比如常用的 Tomcat 会为每次请求单独使用一个线程进行请求处理,同时限制处理请求的线程数量以防止线程过多而崩溃;这很有可能在 CPU 或网络连接没有耗尽之前,线程数量已经耗尽,从而限制了 web 服务的吞吐量。

以上面的请求开启一个线程处理为例,因为 DB或者调用下游服务慢导致响应速度过慢,请求量过大,可能导致我们的线程数量已经使用殆尽,新的请求将被阻塞,但是机器的性能尚有剩余剩余,性能浪费。对于这种需要提高吞吐量的场景,使用虚拟线程将会大大改善这种情况。

使用示例

下面给出一个示例,创建30万个线程,然后都休眠 1 s,如果使用传统的 Thread 线程,可能会因为线程数量不够而直接异常。如果是线程池的方式,会基于线程池的线程数并发,那么剩余线程只能等待,但是使用虚拟线程的方式可以顺利执行完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Jep444VirtualThreadTest {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
// 创建并提交执行虚拟线程
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 300_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}
System.out.println("time:" + (System.currentTimeMillis() - start) + "ms");
}
}

设置虚拟线程名称

1
2
3
4
5
Thread thread1 = Thread.ofVirtual().name("v-thread").unstarted(() -> {
String threadName = Thread.currentThread().getName();
System.out.println(String.format("[%s]Test Virtual Thread", threadName));
});
thread1.start();

输出:[v-thread] Test Virtual Thread

启动为虚拟线程

1
2
3
4
5
Thread thread2 = new Thread(() -> {
String threadName = Thread.currentThread().getName();
System.out.println(String.format("[%s] Test Virtual Thread 2", threadName));
});
Thread.startVirtualThread(thread2);

判断是否是虚拟线程

最后,可以使用 isVirtual 方法判断一个线程对象是否是虚拟线程。

1
2
3
4
5
6
Thread thread1 = Thread.ofVirtual().name("v-thread").unstarted(() -> {
String threadName = Thread.currentThread().getName();
System.out.println(String.format("[%s] Test Virtual Thread", threadName));
});
// 判断是否是虚拟线程
System.out.println(thread1.isVirtual());

有序集合

JDK 21 之前,对几个有序集合的操作示不管是获取第一个元素、获取最后一个元素、还是逆序遍历等,操作方式都不相同,这样很容易混乱。从 JDK 21 开始,增加了 SequencedCollection 接口和 SequencedSet 接口以及 SequencedMap 接口,且 在 SequencedCollection 接口定义了有序集合集中常用的操作方法。

1
2
3
4
5
6
7
addFirst
addLast
getFirst
getLast
removeFirst
removeLast
reversed

SequencedMap 接口中也增加了有序 Map 的常用操作

1
2
3
4
5
6
7
8
9
10
firstEntry
lastEntry
pollFirstEntry
pollLastEntry
putFirst
putLast
reversed
sequencedEntrySet
sequencedKeySet
sequencedValues
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class SequencedCollectionTest {
public static void main(String[] args) {
// JDK 21 之后,为所有元素插入有序集合提供了一致的操作 API
List<Integer> listTemp = List.of(1, 2, 3, 4, 5);

ArrayList<Integer> list = new ArrayList<>(listTemp);
Deque<Integer> deque = new ArrayDeque<>(listTemp);
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>(listTemp);
TreeSet<Integer> sortedSet = new TreeSet<>(listTemp);
LinkedHashMap<Integer, Integer> linkedHashMap = new LinkedHashMap<>();
for (int i = 1; i <= 5; i++) {
linkedHashMap.put(i, i);
}

// 输出第一个元素
System.out.println(list.getFirst());
System.out.println(deque.getFirst());
System.out.println(linkedHashSet.getFirst());
System.out.println(sortedSet.getFirst());
System.out.println(linkedHashMap.firstEntry());
System.out.println("-----------------------");

// 输出最后一个元素
System.out.println(list.getLast());
System.out.println(list.getLast());
System.out.println(deque.getLast());
System.out.println(sortedSet.getLast());
System.out.println(linkedHashMap.lastEntry());
System.out.println("-----------------------");

// 逆序遍历
Consumer<SequencedCollection> printFn = s -> {
// reversed 逆序元素
s.reversed().forEach(System.out::print);
System.out.println();
};
printFn.accept(list);
printFn.accept(deque);
printFn.accept(linkedHashSet);
printFn.accept(sortedSet);
// 有序 map 接口是 SequencedMap,上面的 consume类型不适用
linkedHashMap.reversed().forEach((k, v) -> {
System.out.print(k);
});
}
}