如果你仍然認(rèn)為之前的JDK 17沒有太多改變,那么JDK 21需要引起你的注意。因?yàn)镴DK 21引入了一種新型的并發(fā)編程模型。
目前在Java中的多線程并發(fā)編程是我們頭痛的另一部分。感覺學(xué)起來很困難,使用起來也很復(fù)雜。但是回頭看看使用其他語(yǔ)言的朋友,他們根本沒有這種麻煩,比如GoLang,使用起來非常順暢。
JDK 21在這個(gè)領(lǐng)域取得了巨大的改進(jìn),使Java并發(fā)編程變得更加簡(jiǎn)單和順暢。更準(zhǔn)確地說,這些改進(jìn)在JDK 19或JDK 20中已經(jīng)存在。
1*wtYzgJzJD8rGtysVoIW1cw.png
其中,虛擬線程、作用域值和結(jié)構(gòu)化并發(fā)是多線程并發(fā)編程的一些功能。
虛擬線程是基于協(xié)程的線程,類似于其他語(yǔ)言中的協(xié)程,但也有一些區(qū)別。
虛擬線程附加在主線程上。如果主線程被銷毀,虛擬線程將不再存在。
相似之處:
不同之處:
總的來說,虛擬線程是一種新的線程類型,可以提高應(yīng)用程序的性能和資源利用率,同時(shí)還可以使用傳統(tǒng)的與線程相關(guān)的API。虛擬線程與協(xié)程有許多相似之處,但也存在一些不同之處。
虛擬線程確實(shí)可以使多線程編程更加簡(jiǎn)單和高效。與傳統(tǒng)的操作系統(tǒng)線程相比,創(chuàng)建和銷毀虛擬線程的開銷更小,線程上下文切換的開銷也更小,因此可以大大減少多線程編程中的資源消耗和性能瓶頸。
使用虛擬線程,開發(fā)人員可以像編寫傳統(tǒng)線程代碼一樣編寫代碼,而不必?fù)?dān)心線程的數(shù)量和調(diào)度,因?yàn)镴VM會(huì)自動(dòng)管理虛擬線程的數(shù)量和調(diào)度。此外,虛擬線程還支持傳統(tǒng)的與線程相關(guān)的API,如ThreadLocal、Lock和Semaphore,這使得開發(fā)人員更容易將傳統(tǒng)線程代碼遷移到虛擬線程中。
虛擬線程的引入使多線程編程更加高效、簡(jiǎn)單和安全,允許開發(fā)人員更多關(guān)注業(yè)務(wù)邏輯,而不必過多關(guān)注底層線程管理。
結(jié)構(gòu)化并發(fā)是一種旨在通過提供結(jié)構(gòu)化且易于遵循的方法來簡(jiǎn)化并發(fā)編程的編程范例。使用結(jié)構(gòu)化并發(fā),開發(fā)人員可以創(chuàng)建更容易理解和調(diào)試、不容易出現(xiàn)競(jìng)態(tài)條件和其他與并發(fā)相關(guān)的錯(cuò)誤的并發(fā)代碼。在結(jié)構(gòu)化并發(fā)中,所有并發(fā)代碼都被結(jié)構(gòu)化為稱為任務(wù)的明確定義的工作單元。任務(wù)以結(jié)構(gòu)化的方式創(chuàng)建、執(zhí)行和完成,任務(wù)的執(zhí)行始終保證在其父任務(wù)完成之前完成。
結(jié)構(gòu)化并發(fā)可以使多線程編程更加簡(jiǎn)單和可靠。在傳統(tǒng)的多線程編程中,線程的啟動(dòng)、執(zhí)行和終止都是由開發(fā)人員手動(dòng)管理的,因此容易出現(xiàn)線程泄漏、死鎖和不正確的異常處理等問題。
使用結(jié)構(gòu)化并發(fā),開發(fā)人員可以更自然地組織并發(fā)任務(wù),使任務(wù)之間的依賴關(guān)系更清晰,代碼邏輯更簡(jiǎn)潔。結(jié)構(gòu)化并發(fā)還提供了一些異常處理機(jī)制,以更好地管理并發(fā)任務(wù)中的異常,避免由異常引起的程序崩潰或數(shù)據(jù)不一致。
此外,結(jié)構(gòu)化并發(fā)還可以通過限制并發(fā)任務(wù)的數(shù)量和優(yōu)先級(jí)來防止資源
競(jìng)爭(zhēng)和饑餓現(xiàn)象。這些特性使得開發(fā)人員能夠更容易地實(shí)現(xiàn)高效且可靠的并發(fā)程序,而不必過多關(guān)注底層線程管理。
作用域值是JDK 20中的一項(xiàng)功能,允許開發(fā)人員創(chuàng)建僅限于特定線程或任務(wù)的作用域值。作用域值類似于線程本地變量,但設(shè)計(jì)用于與虛擬線程和結(jié)構(gòu)化并發(fā)一起使用。它們?cè)试S開發(fā)人員以結(jié)構(gòu)化的方式在不同部分的應(yīng)用程序之間傳遞上下文信息,例如用戶身份驗(yàn)證或請(qǐng)求特定數(shù)據(jù)。
在繼續(xù)以下探索之前,您需要至少下載JDK 19或直接下載JDK 20。截止到2023年9月,JDK 20是官方發(fā)布的最高版本。如果使用JDK 19,您將無法體驗(yàn)到Scoped Values功能。
1*GQ22_fxZ-eRKk85BBXuHWQ.png
或者直接下載JDK 21的早期訪問版本。
1*0hHWnZaMVfsKlVCLjMJUSg.png
如果您使用的是IDEA,則您的IDEA版本必須至少為2022.3或更高版本,否則不支持這樣的新JDK版本。
如果您使用的是JDK 19或JDK 20,您應(yīng)該在項(xiàng)目設(shè)置中將語(yǔ)言級(jí)別設(shè)置為19或20。否則,在編譯時(shí)會(huì)提示您無法使用預(yù)覽版本功能。虛擬線程是預(yù)覽版本的功能。
1*6oGVASOHa2kRTtbZ--F7AQ.png
如果您使用的是JDK 21,請(qǐng)將語(yǔ)言級(jí)別設(shè)置為X - 實(shí)驗(yàn)性功能。此外,由于JDK 21不是官方版本,您需要進(jìn)入IDEA設(shè)置(請(qǐng)注意,這是IDEA設(shè)置,而不是項(xiàng)目設(shè)置),并手動(dòng)將項(xiàng)目的目標(biāo)字節(jié)碼版本更改為21。當(dāng)前,最高選項(xiàng)為20,即JDK 20。將其設(shè)置為21后,您可以在JDK 21中使用這些功能。
1*8ltpmMzUzE4u5CAGgNvIOg.png
現(xiàn)在我們?nèi)绾螁?dòng)線程?
首先,聲明一個(gè)線程類,實(shí)現(xiàn)Runnable接口,并實(shí)現(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); } }}
然后,您可以使用這個(gè)線程類并啟動(dòng)線程。
Thread thread = new Thread(new SimpleThread());thread.start();
擁有虛擬線程后,如何實(shí)現(xiàn)呢?
Thread.ofPlatform().name("thread-test").start(new SimpleThread());
以下是使用虛擬線程的幾種方式。
(1) 直接啟動(dòng)虛擬線程
Thread thread = Thread.startVirtualThread(new SimpleThread());
(2) 使用ofVirtual(),構(gòu)建器模式啟動(dòng)虛擬線程,您可以設(shè)置線程名稱、優(yōu)先級(jí)、異常處理和其他配置
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();
想象以下情景。假設(shè)您有三個(gè)任務(wù)需要同時(shí)執(zhí)行。只要任何一個(gè)任務(wù)完成并返回結(jié)果,就可以直接使用該結(jié)果,可以停止其他兩個(gè)任務(wù)。例如,一個(gè)天氣服務(wù)通過三個(gè)渠道獲取天氣情況,只要一個(gè)渠道返回即可。
在這種情況下,在Java 8下應(yīng)該做什么,當(dāng)然也是可以的。
List<Future<?>> futures = executor.invokeAll(tasks);String result = executor.invokeAny(tasks);
使用ExecutorService的invokeAll和invokeAny方法實(shí)現(xiàn),但會(huì)有一些額外的工作。在獲取第一個(gè)結(jié)果后,您需要手動(dòng)關(guān)閉另一個(gè)線程。
在JDK 21中,可以使用結(jié)構(gòu)化編程來實(shí)現(xiàn)。
ShutdownOnSuccess捕獲第一個(gè)結(jié)果并關(guān)閉任務(wù)范圍以中斷未完成的線程并喚醒調(diào)用線程。
一種情況是任何子任務(wù)的結(jié)果都可以直接使用,而無需等待其他未完成任務(wù)的結(jié)果。
它定義了獲取第一個(gè)結(jié)果或在所有子任務(wù)失敗時(shí)拋出異常的方法。
public static void main(String[] args) throws IOException { try (var scope = new StructuredTaskScope.ShutdownOnSuccess()) { Future<String> res1 = scope.fork(() -> runTask(1)); Future<String> res2 = scope.fork(() -> runTask(2)); Future<String> 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í)行多個(gè)任務(wù),只要有一個(gè)失敗(發(fā)生異?;蛞l(fā)其他活動(dòng)異常),就停止其他未完成的任務(wù),并使用scope.throwIfFailed來捕獲并拋出異常。
如果所有任務(wù)都正常,可以使用Feture.get()或*Feture.resultNow()來獲取結(jié)果。
public static void main(String[] args) throws IOException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Future<String> res1 = scope.fork(() -> runTaskWithException(1)); Future<String> res2 = scope.fork(() -> runTaskWithException(2)); Future<String> 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;}
我們一定使用過ThreadLocal,它是線程本地變量,只要線程不銷毀,就可以隨時(shí)獲取ThreadLocal中的變量值。作用域值也可以在線程內(nèi)的任何時(shí)候獲取變量,但它有一個(gè)作用域的概念,當(dāng)超出作用域時(shí)將被銷毀。
public class ScopedValueExample { final static ScopedValue<String> 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()聲明了一個(gè)ScopedValue,使用ScopedValue.where為ScopedValue設(shè)置了一個(gè)值,并使用run方法執(zhí)行接下來要做的事情,以便在run()內(nèi)部隨時(shí)獲取ScopedValue。在run方法中模擬了service的登錄方法,不需要傳遞參數(shù)LoginUser,直接通過LoginUser.get方法可以直接獲取當(dāng)前登錄用戶的值。
本文鏈接:http://www.www897cc.com/showinfo-26-11874-0.html聊一聊Java 21,虛擬線程、結(jié)構(gòu)化并發(fā)和作用域值
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com
上一篇: 一文學(xué)會(huì)隊(duì)列入門:Python數(shù)據(jù)結(jié)構(gòu)與算法