➜ bin pwd
/Users/darcy/develop/jdk-20.0.1.jdk/Contents/Home/bin
➜ bin ./java -version
openjdk version "20.0.1" 2023-04-18
OpenJDK Runtime Environment (build 20.0.1+9-29)
OpenJDK 64-Bit Server VM (build 20.0.1+9-29, mixed mode, sharing)
Java 20 共帶來 7 個新特性功能,其中三個是孵化提案,孵化也就是說尚在徵求意見階段,未來可能會刪除此功能。
JEP | 描述 | 分類 |
---|---|---|
429 | 作用域值(孵化器) | Project Loom,Java 開發相關 |
432 | Record 模式匹配(第二次預覽) | Project Amber,新的語言特性 |
433 | switch 的模式匹配(第四次預覽) | Project Amber,新的語言特性 |
434 | 外部函式和記憶體 API(第二個預覽版) | Project Panama,非 Java 庫 |
436 | 虛擬執行緒(第二個預覽版) | Project Loom,Java 開發相關 |
437 | 結構化併發(第二孵化器) | Project Loom,Java 開發相關 |
438 | Vector API(第五孵化器) | Project Panama,非 Java 庫 |
JEP:JDK Enhancement Proposal,JDK 增強建議,或者叫 Java 未來發展建議。
JDK 20 不是長期支援 (LTS) 版本,因此它只會在六個月後被 JDK 21 取代之前收到更新。JDK 17( 2021 年 9 月 14 日釋出)是 Java 的最新 LTS 版本。Oracle 宣佈計劃將 LTS 版本之間的時間從三年縮短到兩年,因此 JDK 21(2023 年 9 月)計劃成為下一個LTS。
Java 20 安裝
Java 20 OpenJDK 下載:https://jdk.java.net/19/
Java 20 OpenJDK 文件:https://openjdk.java.net/projects/jdk/20/
Java 20 OracleJDK 下載:Oracle JDK 20 Archive Downloads
# 此文中示例程式碼執行都在 Java 20 環境下使用命令
➜ src $ /jdk-20.0.1.jdk/Contents/Home/bin/java --version
openjdk 20.0.1 2023-04-18
OpenJDK Runtime Environment (build 20.0.1+9-29)
OpenJDK 64-Bit Server VM (build 20.0.1+9-29, mixed mode, sharing)
➜ src $ /jdk-20.0.1.jdk/Contents/Home/bin/java --add-modules jdk.incubator.concurrent Xxx.java
WARNING: Using incubator modules: jdk.incubator.concurrent
hello wdbyte
沒有資訊
JEP 429: Scoped Value
線上程之間共享變數不是一件簡單的事,可以使用 ThreadLocal
來儲存當前執行緒變數,但是需要手動清理,開發者常常忘記,且變數不能被子執行緒繼承;而使用 InheritableThreadLocal
共享資訊可以被子執行緒繼承,但是資料會複製多份,佔用更多記憶體。
引入Scoped values
,允許線上程內和執行緒間共享不可變資料,這比執行緒區域性變數更加方便,尤其是在使用大量虛擬執行緒時。這提高了易用性、可理解性、健壯性以及效能。不過這是一個正在孵化的 API,未來可能會被刪除。
scoped values
有下面幾個目標:
- 易用性——提供一個程式設計模型來線上程內和子執行緒之間共享資料,從而簡化資料流的推理。
- 可理解性——使共享資料的生命週期從程式碼的句法結構中可見。
- 穩健性——確保呼叫者共享的資料只能由合法的被呼叫者檢索。
- 效能——將共享資料視為不可變的,以便允許大量執行緒共享,並啟用執行時最佳化。
例子
如果每個請求都是用一個單獨的執行緒來處理,現在需要接受一個請求,然後根據不同身份訪問資料庫,那麼我們可以用傳遞引數的方式,直接把身份資訊在呼叫訪問資料庫方法時傳遞過去。如果不這麼做,那麼就要使用 ThreadLocal
來共享變數了。
Thread 1 Thread 2
-------- --------
8. 資料庫 - 開始查詢 () 8. throw new InvalidPrincipalException()
7. 資料庫 - 開始訪問 () <---+ 7. 資料庫 - 開始訪問 () <---+
... | ... |
... 身份(管理員) ... 身份(訪客)
2. 開始處理(..) | 2. 開始處理(..) |
1. 收到請求(..) -----------+ 1. 收到請求(..) -----------+
示意程式碼:
class Server {
final static ThreadLocal<Principal> PRINCIPAL = new ThreadLocal<>();
void serve(Request request, Response response) {
var level = (request.isAuthorized() ? ADMIN : GUEST);
var principal = new Principal(level);
PRINCIPAL.set(principal);
Application.handle(request, response);
}
}
class DBAccess {
DBConnection open() {
var principal = Server.PRINCIPAL.get();
if (!principal.canOpen()) throw new InvalidPrincipalException();
return newConnection(...);
}
}
這是我們常見的寫法,但是使用 ThreadLocal
的問題是:
PRINCIPAL.set(principal)
可以被任意設定修改。- 使用
ThreadLocal
可能會忘記remove
。 - 如果想要子執行緒繼承共享的變數,需要佔用新的記憶體空間。
- 在虛擬執行緒場景下,可能會有幾十萬執行緒,使用
ThreadLocal
過於複雜,且有安全效能隱患。
虛擬執行緒自 Java 19 引入:JEP 425: 虛擬執行緒 (預覽)
使用 ScopedValue
import jdk.incubator.concurrent.ScopedValue;
/**
* 啟動命令加上 --add-modules jdk.incubator.concurrent
*
* @author https://www.wdbyte.com
*/
public class Jep429ScopedValueTest {
final static ScopedValue<String> SCOPED_VALUE = ScopedValue.newInstance();
public static void main(String[] args) {
// 建立執行緒
Thread thread1 = new Thread(Jep429ScopedValueTest::handle);
Thread thread2 = new Thread(Jep429ScopedValueTest::handle);
String str = "hello wdbyte";
// 傳入執行緒裡使用的字串資訊
ScopedValue.where(SCOPED_VALUE, str).run(thread1);
ScopedValue.where(SCOPED_VALUE, str).run(thread2);
// 執行完畢自動清空,這裡獲取不到了。
System.out.println(SCOPED_VALUE.orElse("沒有資訊"));
}
public static void handle() {
String result = SCOPED_VALUE.get();
System.out.println(result);
}
}
執行:
➜ src $ /jdk-20.0.1.jdk/Contents/Home/bin/java --version
openjdk 20.0.1 2023-04-18
OpenJDK Runtime Environment (build 20.0.1+9-29)
OpenJDK 64-Bit Server VM (build 20.0.1+9-29, mixed mode, sharing)
➜ src $ /jdk-20.0.1.jdk/Contents/Home/bin/java --add-modules jdk.incubator.concurrent Jep429ScopedValueTest.java
WARNING: Using incubator modules: jdk.incubator.concurrent
hello wdbyte.com
hello wdbyte.com
沒有資訊
可見使用 ScopedValue
有幾個顯而易見的好處。
- 程式碼方便,容易理解。符合程式設計邏輯。
- 不允許修改值,安全性高。(沒有 set 方法)
- 生命週期明確。只傳遞到
run()
方法體中。 - 不需要清理,自動釋放。
- 從實現來講,也是一種輕量級實現。
JEP 432: Record 模式匹配(二次預覽)
在 Java 14 的 JEP 359 中增加了 Record 類,在 Java 16 的 JEP 394中,新增了 instanceof 模式匹配。
這兩項都簡化了 Java 開發的程式碼編寫。在 Java 19 的 JEP 405 中,增又加了 Record 模式匹配功能的第一次預覽,這把 JEP 359 和 JEP 394 的功能進行了結合,但是還不夠強大,現在 JEP 432 又對其進行了增強。
JEP 359 功能回顧:
/**
* @author https://www.wdbyte.com
*/
public class RecordTest {
public static void main(String[] args) {
Dog dog = new Dog("name", 1);
System.out.println(dog.name()); // name
System.out.println(dog.age()); // 1
}
}
record Dog(String name, Integer age) {
}
JEP 394 功能回顧:
// Old code
if (obj instanceof String) {
String s = (String)obj;
... use s ...
}
// New code
if (obj instanceof String s) {
... use s ...
}
JEP 432 例子
而現在,可以進行更加複雜的組合巢狀,依舊可以準確識別型別。
/**
* @author https://www.wdbyte.com
*/
public class Jep432RecordAndInstance {
public static void main(String[] args) {
ColoredPoint coloredPoint1 = new ColoredPoint(new Point(0, 0), Color.RED);
ColoredPoint coloredPoint2 = new ColoredPoint(new Point(1, 1), Color.GREEN);
Rectangle rectangle = new Rectangle(coloredPoint1, coloredPoint2);
printUpperLeftColoredPoint(rectangle);
}
static void printUpperLeftColoredPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
System.out.println(ul.c());
}
}
}
record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
輸出:RED
。
JEP 433: switch 模式匹配(四次預覽)
Switch 的使用體驗改造早在 Java 17 就已經開始了,下面是之前文章的一些介紹。
現在 JEP 433 進行第四次預覽,對其功能進行了增強,直接從下面的新老程式碼看其變化。
/**
* @author https://www.wdbyte.com
*/
public class JEP433SwitchTest {
public static void main(String[] args) {
Object obj = 123;
System.out.println(matchOld(obj)); // 是個數字
System.out.println(matchNew(obj)); // 是個數字
obj = "wdbyte.com";
System.out.println(matchOld(obj)); // 是個字串,長度大於2
System.out.println(matchNew(obj)); // 是個字串,長度大於2
}
/**
* 老程式碼
*
* @param obj
* @return
*/
public static String matchOld(Object obj) {
if (obj == null) {
return "資料為空";
}
if (obj instanceof String) {
String s = obj.toString();
if (s.length() > 2) {
return "是個字串,長度大於2";
}
if (s.length() <= 2) {
return "是個字串,長度小於等於2";
}
}
if (obj instanceof Integer) {
return "是個數字";
}
throw new IllegalStateException("未知資料:" + obj);
}
/**
* 新程式碼
*
* @param obj
* @return
*/
public static String matchNew(Object obj) {
String res = switch (obj) {
case null -> "資料為空";
case String s when s.length() > 2 -> "是個字串,長度大於2";
case String s when s.length() <= 2 -> "是個字串,長度小於等於於2";
case Integer i -> "是個數字";
default -> throw new IllegalStateException("未知資料:" + obj);
};
return res;
}
}
JEP 434: 外部函式和記憶體 API(二次預覽)
此功能引入的 API 允許 Java 開發者與 JVM 之外的程式碼和資料進行互動,透過呼叫外部函式(JVM 之外)和安全的訪問外部記憶體(非 JVM 管理),讓 Java 程式可以呼叫本機庫並處理本機資料,而不會像 JNI 一樣存在很多安全風險。
這不是一個新功能,自 Java 14 就已經引入,此次對其進行了效能、通用性、安全性、易用性上的最佳化。
歷史
- Java 14 JEP 370 引入了外部記憶體訪問 API(孵化器)。
- Java 15 JEP 383 引入了外部記憶體訪問 API(第二孵化器)。
- Java 16 JEP 389 引入了外部連結器 API(孵化器)。
- Java 16 JEP 393 引入了外部記憶體訪問 API(第三孵化器)。
- Java 17 JEP 412 引入了外部函式和記憶體 API(孵化器)。
- Java 18 JEP 419 引入了外部函式和記憶體 API(二次孵化器)。
- Java 19 JEP 424 引入了外部函式和記憶體 API(孵化器)。
JEP 436: 虛擬執行緒(二次預覽)
透過將輕量級虛擬執行緒引入 Java 平臺,簡化了編寫、維護和觀察高吞吐量、併發應用程式的過程。使開發人員能夠使用現有的 JDK 工具和技術輕鬆地排除故障、除錯和分析併發應用程式,虛擬執行緒有助於加速應用程式開發。
這個特性自 Java 19 的 JEP 425: 虛擬執行緒 (預覽)引入,在 Java 19 已經進行了詳細介紹。
JEP 437: Structured Concurrency(二次孵化)
透過引入用於結構化併發 API 來簡化多執行緒程式設計。結構化併發將在不同執行緒中執行的多個任務視為單個工作單元,從而簡化錯誤處理,提高可靠性,增強可觀察性。因為是個孵化狀態提案,這裡不做過多研究。
- 相關 Java 19,JEP 428:結構化併發(孵化)
JEP 438: Vector API(五次孵化)
再次提高效能,實現優於等效標量計算的效能。這是透過引入一個 API 來表達向量計算,該 API 在執行時可靠地編譯為支援的 CPU 架構上的最佳向量指令,從而實現優於等效標量計算的效能。Vector API 在 JDK 16 到 19 中孵化。JDK 20 整合了這些版本使用者的反饋以及效能改進和實現增強。
一如既往,文章中程式碼存放在 Github.com/niumoo/javaNotes.
文章持續更新,可以微信搜一搜「 程式猿阿朗 」或訪問「程式猿阿朗部落格 」第一時間閱讀。本文 Github.com/niumoo/JavaNotes 已經收錄,有很多系列文章,歡迎Star。