openjdk8 项目结构_OpenJDK织机和结构化并发
openjdk8 項目結(jié)構(gòu)
Project Loom是Hotspot Group贊助的項目之一,旨在向JAVA世界提供高吞吐量和輕量級的并發(fā)模型。 在撰寫本文時,Loom項目仍在積極開發(fā)中,其API可能會更改。
為什么要織機?
每個新項目可能會出現(xiàn)的第一個問題是為什么?
為什么我們需要學習新的東西,它對我們有幫助? (如果確實如此)
因此,要專門針對Loom回答此問題,我們首先需要了解JAVA中現(xiàn)有線程系統(tǒng)如何工作的基礎(chǔ)知識。
JVM內(nèi)部產(chǎn)生的每個線程在OS內(nèi)核空間中都有一個一對一的對應線程,并具有自己的堆棧,寄存器,程序計數(shù)器和狀態(tài)。 每個線程的最大部分可能是堆棧,堆棧大小以兆字節(jié)為單位,通常在1MB到2MB之間。
因此,這些類型的線程在啟動和運行時方面都很昂貴。 不可能在一臺機器上產(chǎn)生1萬個線程并期望它能正常工作。
有人可能會問為什么我們甚至需要那么多線程? 鑒于CPU只有幾個超線程。 例如,CPU Internal Core i9總共有16個線程。
好吧,CPU并不是您的應用程序使用的唯一資源,任何沒有I / O的軟件都只會導致全球變暖!
一旦線程需要I / O,操作系統(tǒng)就會嘗試為其分配所需的資源,并同時調(diào)度另一個需要CPU的線程。 因此,我們在應用程序中擁有的線程越多,我們就越可以并行利用這些資源。
一個非常典型的示例是Web服務器。 每臺服務器能夠在每個時間點處理數(shù)千個打開的連接,但是同時處理多個連接要么需要數(shù)千個線程,要么需要異步非阻塞代碼( 我可能會在接下來的幾周內(nèi)撰寫另一篇文章,以解釋更多有關(guān)異步代碼 ),就像前面提到的,成千上萬個OS線程既不是您也不是OS會滿意的!
織機如何提供幫助?
作為Project Loom的一部分,引入了一種稱為Fiber的新型線程。 光纖也稱為虛擬線程 , 綠色線程或用戶線程,因為這些名稱暗示完全由VM處理,并且OS甚至都不知道此類線程存在。 這意味著并非每個VM線程都需要在OS級別具有相應的線程! 虛擬線程可能被I / O阻塞或等待從另一個線程獲取信號,但是,與此同時,其他虛擬線程也可以利用基礎(chǔ)線程!
上圖說明了虛擬線程和OS線程之間的關(guān)系。 虛擬線程可以簡單地被I / O阻塞,在這種情況下,基礎(chǔ)線程將被另一個虛擬線程使用。
這些虛擬線程的內(nèi)存占用量將以千字節(jié)為單位,而不是兆字節(jié)。 如果需要,可以在生成它們之后擴展它們的堆棧,這樣JVM不需要為它們分配大量內(nèi)存。
因此,既然我們有一種非常輕巧的方式來實現(xiàn)并發(fā),我們就可以重新考慮存在于Java經(jīng)典線程中的最佳實踐。
如今,在Java中實現(xiàn)并發(fā)最常用的結(jié)構(gòu)是ExecutorService的不同實現(xiàn)。 它們具有非常方便的API,并且相對易于使用。 執(zhí)行程序服務具有一個內(nèi)部線程池,用于根據(jù)開發(fā)人員定義的特征來控制可以產(chǎn)生多少個線程。 該線程池主要用于限制應用程序創(chuàng)建的OS線程的數(shù)量,因為如上所述,它們是昂貴的資源,我們應該盡可能地重用它們。 但是現(xiàn)在可以生成輕量級虛擬線程了,我們也可以重新考慮使用ExecutorServices的方式。
結(jié)構(gòu)化并發(fā)
結(jié)構(gòu)化并發(fā)是一種編程范例,是一種編寫易于讀取和維護的并發(fā)程序的結(jié)構(gòu)化方法。 如果代碼對并發(fā)任務有明確的入口和出口點,則其主要思想與結(jié)構(gòu)化編程非常相似,與啟動可能比當前作用域更長的并發(fā)任務相比,對代碼的推理要容易得多!
為了更清楚地了解結(jié)構(gòu)化并發(fā)代碼的外觀,請考慮以下偽代碼:
void notifyUser(User user) { try (var scope = new ConcurrencyScope()) { scope.submit( () -> notifyByEmail(user)); scope.submit( () -> notifyBySMS(user)); } LOGGER.info( "User has been notified successfully" ); }notifyUser方法應該通過電子郵件和SMS通知用戶,一旦成功完成此方法將記錄一條消息。 使用結(jié)構(gòu)化并發(fā),可以保證在兩種通知方法都完成后立即寫入日志。 換句話說,如果嘗試范圍在其中所有已啟動的并發(fā)作業(yè)都完成了,那么它將完成!
注意:為了使示例簡單,我們假設(shè)notifyByEmail和notifyBySMS在上面的示例中,在內(nèi)部確實處理所有可能的極端情況,并始終使其通過。
JAVA的結(jié)構(gòu)化并發(fā)
在本節(jié)中,我將通過一個非常簡單的示例展示如何用JAVA編寫結(jié)構(gòu)化并發(fā)應用程序以及Fibers如何幫助擴展應用程序。
我們要解決的問題
想象一下,所有I / O綁定有1萬個任務,而每個任務恰好需要100毫秒才能完成。 我們被要求編寫高效的代碼來完成這些工作。
我們使用下面定義的Job類來模仿我們的工作。
public class Job { public void doIt() { try { Thread.sleep(100l); } catch (InterruptedException e) { e.printStackTrace(); } } }第一次嘗試
在第一次嘗試中,我們使用緩存線程池和OS線程來編寫它。
public class ThreadBasedJobRunner implements JobRunner { @Override public long run(List<Job> jobs) { var start = System.nanoTime(); var executor = Executors.newCachedThreadPool(); for (Job job : jobs) { executor.submit(job::doIt); } executor.shutdown(); try { executor.awaitTermination( 1 , TimeUnit.DAYS); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } var end = System.nanoTime(); long timeSpentInMS = Util.nanoToMS(end - start); ?return timeSpentInMS; } }在此嘗試中,我們沒有應用Loom項目中的任何內(nèi)容。 只是一個緩存的線程池,以確保將使用空閑線程,而不是創(chuàng)建新線程。
讓我們看看使用此實現(xiàn)可以運行10,000個作業(yè)所需的時間。 我使用下面的代碼來查找運行速度最快的10個代碼。 為簡單起見,未使用任何微基準測試工具。
public class ThreadSleep { public static void main(String[] args) throws InterruptedException { List<Long> timeSpents = new ArrayList<>( 100 ); var jobs = IntStream.range( 0 , 10000 ).mapToObj(n -> new Job()).collect(toList()); for ( int c = 0 ; c <= 100 ; c++) { var jobRunner = new var jobRunner = ThreadBasedJobRunner(); var timeSpent = jobRunner.run(jobs); timeSpents.add(timeSpent); } Collections.sort(timeSpents); System.out.println( "Top 10 executions took:" ); timeSpents.stream().limit( 10 ) .forEach(timeSpent -> System.out.println( "%s ms" .formatted(timeSpent)) ); } }我的機器上的結(jié)果是:
執(zhí)行死刑前10名:
694毫秒
695毫秒 696毫秒 696毫秒 696毫秒 697毫秒 699毫秒 700毫秒 700毫秒 700毫秒
到目前為止,我們有一個代碼,在最好的情況下,大約需要700毫秒才能在我的計算機上運行10,000個作業(yè)。 讓我們這次使用Loom功能實現(xiàn)JobRunner。
第二次嘗試(使用光纖)
在使用Fibers或Virtual Threads的實現(xiàn)中,我還將以結(jié)構(gòu)化方式對并發(fā)進行編碼。
public class FiberBasedJobRunner implements JobRunner { @Override public long run(List<Job> jobs) { var start = System.nanoTime(); var factory = Thread.builder().virtual().factory(); try (var executor = Executors.newUnboundedExecutor(factory)) { for (Job job : jobs) { executor.submit(job::doIt); } } var end = System.nanoTime(); long timeSpentInMS = Util.nanoToMS(end - start); return timeSpentInMS; } }也許關(guān)于此實現(xiàn)的第一個值得注意的事情是它的簡潔性,如果將其與ThreadBasedJobRunner進行比較,您會發(fā)現(xiàn)該代碼的行數(shù)更少! 主要原因是ExecutorService接口中的新更改現(xiàn)在擴展了Autocloseable ,因此,我們可以在try-with-resources范圍中使用它。 所有提交的作業(yè)完成后,將執(zhí)行try塊之后的代碼。
這正是我們用來在JAVA中編寫結(jié)構(gòu)化并發(fā)代碼的主要結(jié)構(gòu)。
上面代碼中的另一個新事物是我們構(gòu)建線程工廠的新方法。 Thread類有一個稱為builder的新靜態(tài)方法,可用于創(chuàng)建Thread或ThreadFactory 。
該行代碼正在創(chuàng)建一個創(chuàng)建虛擬線程的線程工廠。
現(xiàn)在,讓我們看看使用此實現(xiàn)可以運行10,000個作業(yè)所需的時間。
執(zhí)行死刑前10名:
121毫秒
122毫秒 122毫秒 123毫秒 124毫秒 124毫秒 124毫秒 125毫秒 125毫秒 125毫秒
鑒于Project Loom仍在積極開發(fā)中,并且仍有提高速度的空間,但結(jié)果確實很棒。
不論是全部還是部分應用,都可以以最小的努力受益于Fibers! 唯一需要更改的是線程池的線程工廠 ,僅此而已!
具體來說,在此示例中,應用程序的運行速度提高了約6倍,但是,速度并不是我們在這里獲得的唯一結(jié)果!
盡管我不想寫有關(guān)使用Fibers大大減少了的應用程序的內(nèi)存占用的信息,但是我強烈建議您在此訪問本文的代碼,并比較使用的內(nèi)存量和每個實現(xiàn)占用的OS線程數(shù)! 您可以在此處下載Loom的官方早期試用版。
在接下來的帖子中,我將詳細介紹Loom引入的其他API項目,以及我們?nèi)绾螌⑵鋺糜诂F(xiàn)實生活中的用例。
請不要猶豫,通過評論與我分享您的反饋意見
翻譯自: https://www.javacodegeeks.com/2020/02/openjdk-loom-and-structured-concurrency.html
openjdk8 項目結(jié)構(gòu)
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的openjdk8 项目结构_OpenJDK织机和结构化并发的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无私是什么意思 无私的含义
- 下一篇: java 正则表达式 开头_如何在Jav