新聞中心
如果你仍然認為之前的JDK 17沒有太多改變,那么JDK 21需要引起你的注意。因為JDK 21引入了一種新型的并發(fā)編程模型。

成都創(chuàng)新互聯(lián)公司堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的江漢網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
目前在Java中的多線程并發(fā)編程是我們頭痛的另一部分。感覺學(xué)起來很困難,使用起來也很復(fù)雜。但是回頭看看使用其他語言的朋友,他們根本沒有這種麻煩,比如GoLang,使用起來非常順暢。
JDK 21在這個領(lǐng)域取得了巨大的改進,使Java并發(fā)編程變得更加簡單和順暢。更準確地說,這些改進在JDK 19或JDK 20中已經(jīng)存在。
1*wtYzgJzJD8rGtysVoIW1cw.png
其中,虛擬線程、作用域值和結(jié)構(gòu)化并發(fā)是多線程并發(fā)編程的一些功能。
一、虛擬線程
虛擬線程是基于協(xié)程的線程,類似于其他語言中的協(xié)程,但也有一些區(qū)別。
虛擬線程附加在主線程上。如果主線程被銷毀,虛擬線程將不再存在。
相似之處:
- 虛擬線程和協(xié)程都很輕量級,它們的創(chuàng)建和銷毀開銷比傳統(tǒng)操作系統(tǒng)線程要小。
- 虛擬線程和協(xié)程都可以通過掛起和恢復(fù)來在線程之間切換,從而避免了線程上下文切換的開銷。
- 虛擬線程和協(xié)程都可以以異步和非阻塞的方式處理任務(wù),提高了應(yīng)用程序的性能和響應(yīng)能力。
不同之處:
- 虛擬線程是在JVM級別實現(xiàn)的,而協(xié)程是在語言級別實現(xiàn)的。因此,虛擬線程的實現(xiàn)可以與支持JVM的任何語言一起使用,而協(xié)程的實現(xiàn)需要特定的編程語言支持。
- 虛擬線程是協(xié)程的基于線程的實現(xiàn),因此它們可以使用與線程相關(guān)的API,如ThreadLocal、Lock和Semaphore。協(xié)程不依賴于線程,通常需要特定的異步編程框架和API。
- 虛擬線程的調(diào)度由JVM管理,而協(xié)程的調(diào)度由編程語言或異步編程框架管理。因此,虛擬線程可以更好地與其他線程合作,而協(xié)程更適合處理異步任務(wù)。
總的來說,虛擬線程是一種新的線程類型,可以提高應(yīng)用程序的性能和資源利用率,同時還可以使用傳統(tǒng)的與線程相關(guān)的API。虛擬線程與協(xié)程有許多相似之處,但也存在一些不同之處。
虛擬線程確實可以使多線程編程更加簡單和高效。與傳統(tǒng)的操作系統(tǒng)線程相比,創(chuàng)建和銷毀虛擬線程的開銷更小,線程上下文切換的開銷也更小,因此可以大大減少多線程編程中的資源消耗和性能瓶頸。
使用虛擬線程,開發(fā)人員可以像編寫傳統(tǒng)線程代碼一樣編寫代碼,而不必擔(dān)心線程的數(shù)量和調(diào)度,因為JVM會自動管理虛擬線程的數(shù)量和調(diào)度。此外,虛擬線程還支持傳統(tǒng)的與線程相關(guān)的API,如ThreadLocal、Lock和Semaphore,這使得開發(fā)人員更容易將傳統(tǒng)線程代碼遷移到虛擬線程中。
虛擬線程的引入使多線程編程更加高效、簡單和安全,允許開發(fā)人員更多關(guān)注業(yè)務(wù)邏輯,而不必過多關(guān)注底層線程管理。
二、結(jié)構(gòu)化并發(fā)
結(jié)構(gòu)化并發(fā)是一種旨在通過提供結(jié)構(gòu)化且易于遵循的方法來簡化并發(fā)編程的編程范例。使用結(jié)構(gòu)化并發(fā),開發(fā)人員可以創(chuàng)建更容易理解和調(diào)試、不容易出現(xiàn)競態(tài)條件和其他與并發(fā)相關(guān)的錯誤的并發(fā)代碼。在結(jié)構(gòu)化并發(fā)中,所有并發(fā)代碼都被結(jié)構(gòu)化為稱為任務(wù)的明確定義的工作單元。任務(wù)以結(jié)構(gòu)化的方式創(chuàng)建、執(zhí)行和完成,任務(wù)的執(zhí)行始終保證在其父任務(wù)完成之前完成。
結(jié)構(gòu)化并發(fā)可以使多線程編程更加簡單和可靠。在傳統(tǒng)的多線程編程中,線程的啟動、執(zhí)行和終止都是由開發(fā)人員手動管理的,因此容易出現(xiàn)線程泄漏、死鎖和不正確的異常處理等問題。
使用結(jié)構(gòu)化并發(fā),開發(fā)人員可以更自然地組織并發(fā)任務(wù),使任務(wù)之間的依賴關(guān)系更清晰,代碼邏輯更簡潔。結(jié)構(gòu)化并發(fā)還提供了一些異常處理機制,以更好地管理并發(fā)任務(wù)中的異常,避免由異常引起的程序崩潰或數(shù)據(jù)不一致。
此外,結(jié)構(gòu)化并發(fā)還可以通過限制并發(fā)任務(wù)的數(shù)量和優(yōu)先級來防止資源
競爭和饑餓現(xiàn)象。這些特性使得開發(fā)人員能夠更容易地實現(xiàn)高效且可靠的并發(fā)程序,而不必過多關(guān)注底層線程管理。
三、作用域值
作用域值是JDK 20中的一項功能,允許開發(fā)人員創(chuàng)建僅限于特定線程或任務(wù)的作用域值。作用域值類似于線程本地變量,但設(shè)計用于與虛擬線程和結(jié)構(gòu)化并發(fā)一起使用。它們允許開發(fā)人員以結(jié)構(gòu)化的方式在不同部分的應(yīng)用程序之間傳遞上下文信息,例如用戶身份驗證或請求特定數(shù)據(jù)。
四、實踐
在繼續(xù)以下探索之前,您需要至少下載JDK 19或直接下載JDK 20。截止到2023年9月,JDK 20是官方發(fā)布的最高版本。如果使用JDK 19,您將無法體驗到Scoped Values功能。
1*GQ22_fxZ-eRKk85BBXuHWQ.png
或者直接下載JDK 21的早期訪問版本。
1*0hHWnZaMVfsKlVCLjMJUSg.png
如果您使用的是IDEA,則您的IDEA版本必須至少為2022.3或更高版本,否則不支持這樣的新JDK版本。
如果您使用的是JDK 19或JDK 20,您應(yīng)該在項目設(shè)置中將語言級別設(shè)置為19或20。否則,在編譯時會提示您無法使用預(yù)覽版本功能。虛擬線程是預(yù)覽版本的功能。
1*6oGVASOHa2kRTtbZ--F7AQ.png
如果您使用的是JDK 21,請將語言級別設(shè)置為X - 實驗性功能。此外,由于JDK 21不是官方版本,您需要進入IDEA設(shè)置(請注意,這是IDEA設(shè)置,而不是項目設(shè)置),并手動將項目的目標(biāo)字節(jié)碼版本更改為21。當(dāng)前,最高選項為20,即JDK 20。將其設(shè)置為21后,您可以在JDK 21中使用這些功能。
1*8ltpmMzUzE4u5CAGgNvIOg.png
1. 虛擬線程
現(xiàn)在我們?nèi)绾螁泳€程?
首先,聲明一個線程類,實現(xiàn)Runnable接口,并實現(xiàn)run方法。
public class SimpleThread implements Runnable {
@Override
public void run() {
System.out.println("name:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}然后,您可以使用這個線程類并啟動線程。
Thread thread = new Thread(new SimpleThread());
thread.start();擁有虛擬線程后,如何實現(xiàn)呢?
Thread.ofPlatform().name("thread-test").start(new SimpleThread());以下是使用虛擬線程的幾種方式。
(1) 直接啟動虛擬線程
Thread thread = Thread.startVirtualThread(new SimpleThread());(2) 使用ofVirtual(),構(gòu)建器模式啟動虛擬線程,您可以設(shè)置線程名稱、優(yōu)先級、異常處理和其他配置
Thread.ofVirtual()
.name("thread-test")
.start(new SimpleThread());或者:
Thread thread = Thread.ofVirtual()
.name("thread-test")
.uncaughtExceptionHandler((t, e) -> {
System.out.println(t.getName() + e.getMessage());
})
.unstarted(new SimpleThread());
thread.start();(3) 使用工廠創(chuàng)建線程
ThreadFactory factory = Thread.ofVirtual().factory();
Thread thread = factory.newThread(new SimpleThread());
thread.setName("thread-test");
thread.start();(4) 使用Executors
ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
Future> submit = executorService.submit(new SimpleThread());
Object o = submit.get();2. 結(jié)構(gòu)化并發(fā)
想象以下情景。假設(shè)您有三個任務(wù)需要同時執(zhí)行。只要任何一個任務(wù)完成并返回結(jié)果,就可以直接使用該結(jié)果,可以停止其他兩個任務(wù)。例如,一個天氣服務(wù)通過三個渠道獲取天氣情況,只要一個渠道返回即可。
在這種情況下,在Java 8下應(yīng)該做什么,當(dāng)然也是可以的。
List> futures = executor.invokeAll(tasks);
String result = executor.invokeAny(tasks); 使用ExecutorService的invokeAll和invokeAny方法實現(xiàn),但會有一些額外的工作。在獲取第一個結(jié)果后,您需要手動關(guān)閉另一個線程。
在JDK 21中,可以使用結(jié)構(gòu)化編程來實現(xiàn)。
ShutdownOnSuccess捕獲第一個結(jié)果并關(guān)閉任務(wù)范圍以中斷未完成的線程并喚醒調(diào)用線程。
一種情況是任何子任務(wù)的結(jié)果都可以直接使用,而無需等待其他未完成任務(wù)的結(jié)果。
它定義了獲取第一個結(jié)果或在所有子任務(wù)失敗時拋出異常的方法。
public static void main(String[] args) throws IOException {
try (var scope = new StructuredTaskScope.ShutdownOnSuccess()) {
Future res1 = scope.fork(() -> runTask(1));
Future res2 = scope.fork(() -> runTask(2));
Future res3 = scope.fork(() -> runTask(3));
scope.join();
System.out.println("scope:" + scope.result());
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
}
public static String runTask(int i) throws InterruptedException {
Thread.sleep(
1000);
long l = new Random().nextLong();
String s = String.valueOf(l);
System.out.println(i + "task:" + s);
return s;
} ShutdownOnFailure
執(zhí)行多個任務(wù),只要有一個失?。òl(fā)生異?;蛞l(fā)其他活動異常),就停止其他未完成的任務(wù),并使用scope.throwIfFailed來捕獲并拋出異常。
如果所有任務(wù)都正常,可以使用Feture.get()或*Feture.resultNow()來獲取結(jié)果。
public static void main(String[] args) throws IOException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future res1 = scope.fork(() -> runTaskWithException(1));
Future res2 = scope.fork(() -> runTaskWithException(2));
Future res3 = scope.fork(() -> runTaskWithException(3));
scope.join();
scope.throwIfFailed(Exception::new);
String s = res1.resultNow();
System.out.println(s);
String result = Stream.of(res1, res2, res3)
.map(Future::resultNow)
.collect(Collectors.joining());
System.out.println("result:" + result);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String runTaskWithException(int i) throws InterruptedException {
Thread.sleep(1000);
long l = new Random().nextLong(3);
if (l == 0) {
throw new InterruptedException();
}
String s = String.valueOf(l);
System.out.println(i + "task:" + s);
return s;
} 3. 作用域值
我們一定使用過ThreadLocal,它是線程本地變量,只要線程不銷毀,就可以隨時獲取ThreadLocal中的變量值。作用域值也可以在線程內(nèi)的任何時候獲取變量,但它有一個作用域的概念,當(dāng)超出作用域時將被銷毀。
public class ScopedValueExample {
final static ScopedValue LoginUser = ScopedValue.newInstance();
public static void main(String[] args) throws InterruptedException {
ScopedValue.where(LoginUser, "Tom")
.run(() -> {
new Service().login();
});
Thread.sleep(2000);
}
static class Service {
void login() {
System.out.println("user:" + LoginUser.get());
}
}
} 上面的示例模擬了用戶登錄過程,使用ScopedValue.newInstance()聲明了一個ScopedValue,使用ScopedValue.where為ScopedValue設(shè)置了一個值,并使用run方法執(zhí)行接下來要做的事情,以便在run()內(nèi)部隨時獲取ScopedValue。在run方法中模擬了service的登錄方法,不需要傳遞參數(shù)LoginUser,直接通過LoginUser.get方法可以直接獲取當(dāng)前登錄用戶的值。
本文名稱:聊一聊Java21,虛擬線程、結(jié)構(gòu)化并發(fā)和作用域值
當(dāng)前地址:http://m.fisionsoft.com.cn/article/dpcsgjh.html


咨詢
建站咨詢
