相關 JEP:html
Project Loom 主要目標是在 Java 平臺上提供一種易於使用、高吞吐量的輕量級併發性和新的編程模型的 JVM 特性和API。這帶來了許多有趣和使人興奮的前景,其中之一是簡化網絡交互的代碼的同時兼顧性能。如今的服務器可以處理打開的套接字鏈接的數量,遠遠超過它們可以支持的線程數量,這既帶來了機遇,也帶來了挑戰。java
可是不幸的是,編寫與網絡交互的可伸縮代碼是很困難的。咱們通常使用同步 API 的方式進行編碼,可是在超過必定閾值以後,同步代碼就迎來了瓶頸,很難進行伸縮。由於這樣的API在執行 I/O 操做時會阻塞,而 I/O 操做又會將線程綁定起來,直到操做就緒,例如嘗試從套接字讀取數據可是當前並無數據要讀取的時候。目前的線程,在 Java 平臺中是一個昂貴的資源,以致於沒法等待 I/O 操做的完成再去釋放。爲了解決這個限制,咱們一般使用異步 I/O 或 Ractor 框架,由於它們能夠構造出在 I/O 操做中不用綁定線程的代碼,而是在 I/O 操做完成或準備就緒時使用回調或事件通知線程進行處理。linux
使用異步和非阻塞 API 比使用同步 API 更具備挑戰性,部分緣由是用這些 API 寫出來的代碼是比較反人類的。同步API在很大程度上更容易使用;代碼更易於編寫、更容易閱讀和更易於調試,調試的時候堆棧裏面的信息大部分是有用的。可是如前所述,使用同步 API 的代碼不能像異步代碼那樣伸縮擴展,所以咱們必須作一個艱難的選擇:選擇更簡單的同步代碼,並接受它不會擴展;或者選擇更可伸縮的異步代碼,並處理全部的複雜性。兩個都不是個好選擇!Project Loom 主要就是要讓同步代碼也能靈活伸縮擴展。git
在本文中,咱們將查看 Java 平臺的網絡 API 在虛擬線程上被調用時是如何工做的。瞭解底層細節,咱們才能更好地、更放心地使用虛擬線程(纖程)。github
在進一步研究以前,咱們須要瞭解一下ProjectLoom中的新線程--虛擬線程(也能夠稱爲纖程)。算法
虛擬線程是用戶態線程,被 JVM 管理,而不是操做系統。虛擬線程佔用的系統資源不多,一個 JVM 能夠容納百萬量級的虛擬線程。特別適合於常常執行阻塞時間比較長,常常等待 IO 的任務。編程
平臺線程(即目前 Java 平臺的線程),是和操做系統內核線程一一對應的。平臺線程一般擁有一個很是大的棧,以及其餘的一些系統維護的資源。虛擬線程則使用一小組用做載體線程的平臺線程。在虛擬線程中執行的代碼一般不會知道底層承載的線程。鎖和 I/O 操做是將承載線程從一個虛擬線程從新調度到另外一個虛擬線程的調度點。虛擬線程可能會 parked(例如LockSupport.park()
),從而使其沒法調度。一個已 parked 的虛擬線程可能被取消(例如LockSupport.unpark(Thread)
),這樣從新啓用了它的調度。服務器
Java 平臺中主要有兩種網絡 API:網絡
AsynchronousServerSocketChannel
、AsynchronousSocketChannel
java.net.Socket
、java.net.ServerSocket
、java.net.DatagramSocket
、java.nio.channels.SocketChannel
、java.nio.channels.ServerSocketChannel
、java.nio.channels.DatagramChannel
第一類異步 API,建立啓動在以後某個時間完成的 I/O 操做,可能在啓動 I/O 操做的線程以外的線程上完成。根據定義,這些 API 不會致使阻塞的系統調用,所以在虛擬線程中運行時不須要特殊處理併發
第二類同步 API,從它們在虛擬線程中運行時的行爲角度來看,它們更有趣。在這些 API 中,NIO channel 相關的能夠配置成爲非阻塞模式。這種 channel 一般使用 I/O 事件通知機制實現,例如註冊到 Selector 上監聽事件。相似於異步網絡 API,在虛擬線程中執行不須要額外處理,由於 I/O 操做不本身調用阻塞的系統調用,這個調用留給了 Selector。最後,咱們來看看將 channel 配置成爲阻塞模式以及 java.net
相關 API 的狀況(咱們這裏稱這種 API 爲同步阻塞 API)。同步 API 的語義要求 I/O 操做一旦啓動,在調用線程中完成或失敗,而後將控制權返回給調用方。可是,若是 I/O 操做「還沒有準備好」怎麼辦呢?例如,目前沒有數據能夠讀取。
在虛擬線程中運行的 Java 同步網絡 API 會將底層原生 Socket 切換到非阻塞模式。當 Java 代碼啓用一個 I/O 請求而且這個請求沒有當即完成(原生 socket 返回 EAGAIN - 表明"未就緒"/"會阻塞")的時候,這個底層 socket 會被註冊到一個 JVM 內部事件通知機制(Poller),而且虛擬線程會被 parked。當底層 I/O 操做就緒的時候(有相關事件會到達 Poller),虛擬線程會 unparked 而且底層的 Socket 操做會被重試處理。
咱們來用一個例子仔細看下這其中的原理,首先,咱們須要下載 project loom 的 JDK(地址:http://jdk.java.net/loom/),...
接下來編寫代碼:
//Java 16 中的 Record 對象,能夠理解爲有包含兩個 final 屬性(url 和 response)的類 static record URLData (URL url, byte[] response) { } static List<URLData> retrieveURLs(URL... urls) throws Exception { //建立虛擬線程線程池 try (var executor = Executors.newVirtualThreadExecutor()) { //生成讀取對每一個 url 執行 getURL 方法的任務 var tasks = Arrays.stream(urls) .map(url -> (Callable<URLData>)() -> getURL(url)) .toList(); //提交任務,等待並返回全部結果 return executor.submit(tasks) .filter(Future::isCompletedNormally) .map(Future::join) .toList(); } } //讀取url的內容 static URLData getURL(URL url) throws IOException { try (InputStream in = url.openStream()) { return new URLData(url, in.readAllBytes()); } } public static void main(String[] args) throws Exception { //訪問 google,因爲你懂得,會比較慢 List<URLData> urlData = retrieveURLs(new URL("https://www.google.com/")); }
咱們使用 retrieveURLs
訪問谷歌,確定會很慢,來保證能採集到堆棧。同時,不能用 jstack 採集堆棧(目前 jstack 採集不到虛擬線程堆棧,只能採集到承載線程的堆棧),須要用 jcmd 命令中的 JavaThread.dump
採集。同時,爲了能採集到咱們想要的堆棧,咱們須要一些小操做。
首先,咱們在 getURL(URL url)
方法的第一行打斷點,debug 到這裏暫停。而後執行命令:
> jps 25496 LoomThreadMain 12512 Jps > jcmd 25496 JavaThread.dump threads.txt -overwrite
而後繼續執行程序,再執行命令,採集虛擬線程執行 I/O 操做時候的堆棧:
> jcmd 25496 JavaThread.dump threads2.txt -overwrite
咱們查看threads.txt
這個文件,其中咱們關心的線程信息是:
"main" #1 java.base@17-loom/jdk.internal.misc.Unsafe.park(Native Method) java.base@17-loom/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371) java.base@17-loom/java.util.concurrent.LinkedTransferQueue$Node.block(LinkedTransferQueue.java:470) java.base@17-loom/java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3470) java.base@17-loom/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3441) java.base@17-loom/java.util.concurrent.LinkedTransferQueue.awaitMatch(LinkedTransferQueue.java:669) java.base@17-loom/java.util.concurrent.LinkedTransferQueue.xfer(LinkedTransferQueue.java:616) java.base@17-loom/java.util.concurrent.LinkedTransferQueue.take(LinkedTransferQueue.java:1286) java.base@17-loom/java.util.concurrent.ExecutorServiceHelper$BlockingQueueSpliterator.tryAdvance(ExecutorServiceHelper.java:197) java.base@17-loom/java.util.Spliterator.forEachRemaining(Spliterator.java:326) java.base@17-loom/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) java.base@17-loom/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) java.base@17-loom/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:550) java.base@17-loom/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) java.base@17-loom/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) java.base@17-loom/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) java.base@17-loom/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) app//com.github.hashjang.LoomThreadMain.retrieveURLs(LoomThreadMain.java:43) app//com.github.hashjang.LoomThreadMain.main(LoomThreadMain.java:29) "ForkJoinPool-1-worker-1" #27 java.base@17-loom/java.lang.Continuation.run(Continuation.java:300) java.base@17-loom/java.lang.VirtualThread.runContinuation(VirtualThread.java:240) java.base@17-loom/java.lang.VirtualThread$$Lambda$25/0x0000000801053fc0.run(Unknown Source) java.base@17-loom/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1395) java.base@17-loom/java.util.concurrent.ForkJoinTask.doExec$$$capture(ForkJoinTask.java:373) java.base@17-loom/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java) java.base@17-loom/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1177) java.base@17-loom/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1648) java.base@17-loom/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1615) java.base@17-loom/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) "<unnamed>" #26 virtual java.base/java.util.concurrent.ConcurrentHashMap.transfer(ConcurrentHashMap.java:2431) java.base/java.util.concurrent.ConcurrentHashMap.addCount(ConcurrentHashMap.java:2354) java.base/java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1075) java.base/java.util.concurrent.ConcurrentHashMap.putIfAbsent(ConcurrentHashMap.java:1541) java.base/sun.util.locale.LocaleObjectCache.get(LocaleObjectCache.java:68) java.base/java.util.Locale.getInstance(Locale.java:841) java.base/java.util.Locale.forLanguageTag(Locale.java:1736) java.base/sun.util.locale.provider.LocaleProviderAdapter.toLocaleArray(LocaleProviderAdapter.java:323) java.base/sun.util.locale.provider.CalendarDataProviderImpl.getAvailableLocales(CalendarDataProviderImpl.java:63) java.base/java.util.spi.LocaleServiceProvider.isSupportedLocale(LocaleServiceProvider.java:217) java.base/sun.util.locale.provider.LocaleServiceProviderPool.findProviders(LocaleServiceProviderPool.java:306) java.base/sun.util.locale.provider.LocaleServiceProviderPool.getLocalizedObjectImpl(LocaleServiceProviderPool.java:274) java.base/sun.util.locale.provider.LocaleServiceProviderPool.getLocalizedObject(LocaleServiceProviderPool.java:256) java.base/sun.util.locale.provider.CalendarDataUtility.retrieveFirstDayOfWeek(CalendarDataUtility.java:76) java.base/java.util.Calendar.setWeekCountData(Calendar.java:3419) java.base/java.util.Calendar.<init>(Calendar.java:1612) java.base/java.util.GregorianCalendar.<init>(GregorianCalendar.java:738) java.base/java.util.Calendar$Builder.build(Calendar.java:1494) java.base/sun.util.locale.provider.CalendarProviderImpl.getInstance(CalendarProviderImpl.java:87) java.base/java.util.Calendar.createCalendar(Calendar.java:1697) java.base/java.util.Calendar.getInstance(Calendar.java:1661) java.base/java.text.SimpleDateFormat.initializeCalendar(SimpleDateFormat.java:680) java.base/java.text.SimpleDateFormat.<init>(SimpleDateFormat.java:624) java.base/java.text.SimpleDateFormat.<init>(SimpleDateFormat.java:603) java.base/sun.security.util.DisabledAlgorithmConstraints$DenyAfterConstraint.<clinit>(DisabledAlgorithmConstraints.java:695) java.base/sun.security.util.DisabledAlgorithmConstraints$Constraints.<init>(DisabledAlgorithmConstraints.java:424) java.base/sun.security.util.DisabledAlgorithmConstraints.<init>(DisabledAlgorithmConstraints.java:149) java.base/sun.security.ssl.SSLAlgorithmConstraints.<clinit>(SSLAlgorithmConstraints.java:49) java.base/sun.security.ssl.ProtocolVersion.<init>(ProtocolVersion.java:158) java.base/sun.security.ssl.ProtocolVersion.<clinit>(ProtocolVersion.java:41) java.base/sun.security.ssl.SSLContextImpl$AbstractTLSContext.<clinit>(SSLContextImpl.java:539) java.base/java.lang.Class.forName0(Native Method) java.base/java.lang.Class.forName(Class.java:375) java.base/java.security.Provider$Service.getImplClass(Provider.java:1937) java.base/java.security.Provider$Service.getDefaultConstructor(Provider.java:1968) java.base/java.security.Provider$Service.newInstanceOf(Provider.java:1882) java.base/java.security.Provider$Service.newInstanceUtil(Provider.java:1890) java.base/java.security.Provider$Service.newInstance(Provider.java:1865) java.base/sun.security.jca.GetInstance.getInstance(GetInstance.java:236) java.base/sun.security.jca.GetInstance.getInstance(GetInstance.java:164) java.base/javax.net.ssl.SSLContext.getInstance(SSLContext.java:184) java.base/javax.net.ssl.SSLContext.getDefault(SSLContext.java:110) java.base/javax.net.ssl.SSLSocketFactory.getDefault(SSLSocketFactory.java:83) java.base/javax.net.ssl.HttpsURLConnection.getDefaultSSLSocketFactory(HttpsURLConnection.java:334) java.base/javax.net.ssl.HttpsURLConnection.<init>(HttpsURLConnection.java:291) java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.<init>(HttpsURLConnectionImpl.java:81) java.base/sun.net.www.protocol.https.Handler.openConnection(Handler.java:62) java.base/sun.net.www.protocol.https.Handler.openConnection(Handler.java:57) java.base/java.net.URL.openConnection(URL.java:1093) java.base/java.net.URL.openStream(URL.java:1159) com.github.hashjang.LoomThreadMain.getURL(LoomThreadMain.java:48) com.github.hashjang.LoomThreadMain.lambda$retrieveURLs$0(LoomThreadMain.java:38) java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:295) java.base/java.util.concurrent.FutureTask.run(FutureTask.java) java.base/java.util.concurrent.ThreadExecutor$TaskRunner.run(ThreadExecutor.java:385) java.base/java.lang.VirtualThread.run(VirtualThread.java:295) java.base/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:172) java.base/java.lang.Continuation.enter0(Continuation.java:372) java.base/java.lang.Continuation.enter(Continuation.java:365)
其中 "<unnamed>" #26 virtual
是咱們程序中建立的虛擬線程,而且經過堆棧中能夠看出,虛擬線程尚未處於 I/O 操做。經過線程堆棧也能夠看出,這個虛擬線程的承載線程是 "ForkJoinPool-1-worker-1" #27
. 能夠看出虛擬線程默認的承載線程是 Java 8 以後默認會啓動的 common ForkJoinPool 中的線程。而且是經過 Continuation
這個類執行虛擬線程的工做的。
查看threads2.txt
:
"main" #1 java.base@17-loom/jdk.internal.misc.Unsafe.park(Native Method) java.base@17-loom/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371) java.base@17-loom/java.util.concurrent.LinkedTransferQueue$Node.block(LinkedTransferQueue.java:470) java.base@17-loom/java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3470) java.base@17-loom/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3441) java.base@17-loom/java.util.concurrent.LinkedTransferQueue.awaitMatch(LinkedTransferQueue.java:669) java.base@17-loom/java.util.concurrent.LinkedTransferQueue.xfer(LinkedTransferQueue.java:616) java.base@17-loom/java.util.concurrent.LinkedTransferQueue.take(LinkedTransferQueue.java:1286) java.base@17-loom/java.util.concurrent.ExecutorServiceHelper$BlockingQueueSpliterator.tryAdvance(ExecutorServiceHelper.java:197) java.base@17-loom/java.util.Spliterator.forEachRemaining(Spliterator.java:326) java.base@17-loom/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) java.base@17-loom/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) java.base@17-loom/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:550) java.base@17-loom/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) java.base@17-loom/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) java.base@17-loom/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) java.base@17-loom/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) app//com.github.hashjang.LoomThreadMain.retrieveURLs(LoomThreadMain.java:43) app//com.github.hashjang.LoomThreadMain.main(LoomThreadMain.java:29) "ForkJoinPool-1-worker-1" #25 java.base@17-loom/jdk.internal.misc.Unsafe.park(Native Method) java.base@17-loom/java.util.concurrent.locks.LockSupport.parkUntil(LockSupport.java:449) java.base@17-loom/java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1719) java.base@17-loom/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1616) java.base@17-loom/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) "Read-Poller" #41 java.base@17-loom/sun.nio.ch.WEPoll.wait(Native Method) java.base@17-loom/sun.nio.ch.WEPollPoller.poll(WEPollPoller.java:64) java.base@17-loom/sun.nio.ch.Poller.poll(Poller.java:196) java.base@17-loom/sun.nio.ch.Poller.lambda$startPollerThread$0(Poller.java:66) java.base@17-loom/sun.nio.ch.Poller$$Lambda$89/0x00000008010e5168.run(Unknown Source) java.base@17-loom/java.lang.Thread.run(Thread.java:1521) java.base@17-loom/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:161) "<unnamed>" #24 virtual java.base/java.lang.Continuation.yield(Continuation.java:402) java.base/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:367) java.base/java.lang.VirtualThread.park(VirtualThread.java:534) java.base/java.lang.System$2.parkVirtualThread(System.java:2373) java.base/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:60) java.base/sun.nio.ch.NioSocketImpl.park(NioSocketImpl.java:184) java.base/sun.nio.ch.NioSocketImpl.park(NioSocketImpl.java:212) java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:607) java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:331) java.base/java.net.Socket.connect(Socket.java:642) java.base/sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:299) java.base/sun.security.ssl.BaseSSLSocketImpl.connect(BaseSSLSocketImpl.java:174) java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:182) java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:497) java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:600) java.base/sun.net.www.protocol.https.HttpsClient.<init>(HttpsClient.java:266) java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:380) java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:189) java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1232) java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1120) java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:175) java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1653) java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1577) java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:224) java.base/java.net.URL.openStream(URL.java:1159) com.github.hashjang.LoomThreadMain.getURL(LoomThreadMain.java:48) com.github.hashjang.LoomThreadMain.lambda$retrieveURLs$0(LoomThreadMain.java:38) java.base/java.util.concurrent.FutureTask.run(FutureTask.java:295) java.base/java.util.concurrent.ThreadExecutor$TaskRunner.run(ThreadExecutor.java:385) java.base/java.lang.VirtualThread.run(VirtualThread.java:295) java.base/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:172) java.base/java.lang.Continuation.enter0(Continuation.java:372) java.base/java.lang.Continuation.enter(Continuation.java:365)
經過這裏的線程堆棧能夠看出,虛擬線程在執行 I/O 操做的時候,會調用 java.lang.Continuation.yield
讓出承載線程的資源(至關於 park 住了)。查看原來的承載線程 "ForkJoinPool-1-worker-1" #25
,確實處於空閒狀態了。那麼 I/O 操做去哪裏了呢?這就引出了這個線程 "Read-Poller" #41
。
這個線程是一個 JVM 共用的 read poller。它的核心邏輯是執行一個基本事件循環,監聽全部的同步網絡 read(網絡讀就緒),connect(發起網絡鏈接就緒) 和 accept(接受網絡鏈接就緒) 操做。當這些 I/O 操做就緒的時候,poller 會被通知,而且 unpark 對應的虛擬線程,使得虛擬線程繼續執行。同時,除了 read poller,還有一個用於寫事件的 write poller。
我是使用 Windows 進行測試的,在 Windows 中 poller
底層實現基於 wepoll,因此咱們看到堆棧裏面包含 WEPoll
。對於 MacOS 則是 kqueue,對於 Linux 則是 epoll
poller 維護一個以虛擬線程的文件描述符爲 key 的 map。當一個虛擬線程並將它的文件描述符註冊到 poller 上的時候,會以虛擬線程的文件描述符爲 key,虛擬線程自己爲 value 放入這個 map。當 poller 的事件循環中的相關事件就緒的時候,經過事件中的虛擬線程文件描述符在 map 中找到對應的虛擬線程 unpark 之。
若是簡單來看,上面的設計與使用 NIO Channel 和 Selector 並無太大的不一樣,NIO Channel 和 Selector 能夠在許多服務器端框架和庫中找到,例如 Netty。可是相對來講,NIO Channel 和 Selector 提供了一個更復雜的模型,用戶代碼必須實現事件循環並跨 I/O 邊界維護應用程序邏輯,而虛擬線程則提供了一個更簡單、更直觀的編程模型,Java 平臺負責跨 I/O 邊界調度任務和維護對應的上下文。
如前文咱們看到的那樣,虛擬線程默認的承載線程就是 ForkJoinPool。這是一個很是適合虛擬線程的線程池,工做竊取算法能極致的調度運行虛擬線程。
同步 Java 網絡 API 已經被從新實現,相關的 JEP 包括 JEP 353 和 JEP 373. 在虛擬線程中運行時,不能當即完成的 I/O 操做將致使虛擬線程被 parked。當 I/O 就緒時,虛擬線程將被 unparked。這個實現相對於當前的異步非阻塞 I/O 實現代碼來看,更加簡單易用,隱藏了不少業務不關心的實現細節。