詳解Java8與Runtime.getRuntime().availableProcessors()
lambda表達式以及并行流。官方承諾你寫出來的代碼更運行得更快。流會自動通過Fork/Join池并行地執(zhí)行。我聽過一些關于Java 8的主題的演講,不過在這個非常關鍵的點上它們都說的有點問題。我計劃在后續(xù)的文章中對并行流進行下深入的講解,在這之前我先花點時間仔細地分析下它。關于這個問題,我只想問你們一個非常簡單的問題,不過也是一個非常重要的問題,因為它是很多問題的關鍵所在。這個問題是:
這些并行操作的線程都是從哪來的?
在Java 8里,我們有一個通用的Fork/Join池,我們可以通過ForkJoinPool.commonPool()來訪問它。并行流,并行排序,CompletableFuture等都會用到它。當你構造一個Fork/Join池的時候,通常你都沒有指定最大線程數(shù)。你只是指定了一個期望的并發(fā)數(shù),也就是說你希望在運行時的同一時間有多少活躍的線程。當線程被阻塞在一個phaser的時候,會創(chuàng)建另一個線程來保證池里有足夠的活躍線程。這個phaser就是觸發(fā)這個行為的同步器。Fork/Join池最大的線程數(shù)是32767,但在遠沒達到這個數(shù)量時,在大多數(shù)操作系統(tǒng)上就會拋出OutOfMemoryError異常了。在這段示例代碼中,我會不斷創(chuàng)建新的RecursiveAction真到達到第一個階段(也就是到達了200個線程)。如果我們增加到一個更大的數(shù)字,比如說到100000,這段代碼就會失敗了。
import java.util.concurrent.*; public class PhaserForkJoin { public static void main(String... args) { ForkJoinPool common = ForkJoinPool.commonPool(); Phaser phaser = new Phaser(200); common.invoke(new PhaserWaiter(phaser)); } private static class PhaserWaiter extends RecursiveAction { private final Phaser phaser; private PhaserWaiter(Phaser phaser) { this.phaser = phaser; System.out.println(ForkJoinPool.commonPool().getPoolSize()); } protected void compute() { if (phaser.getPhase() > 0) return; // we've passed first phase PhaserWaiter p1 = new PhaserWaiter(phaser); p1.fork(); phaser.arriveAndAwaitAdvance(); p1.join(); } } }
Fork/Join池沒有一個最大線程數(shù),只有一個期望并發(fā)數(shù),這是指我們希望同時有多少個活躍線程。
通用池是很有用的,因為它意味著不同類型的作業(yè)可以共享同一個池,而不用超出代碼所運行的機器上期望并發(fā)數(shù)。當然了,如果一個線程由于非Phaser的其它原因阻塞了,那可能這個通用池的表現(xiàn)就和預期的不太一樣了。
什么是通用FJ池的默認的期望并發(fā)數(shù)?
通常的FJ池的期望并發(fā)數(shù)的默認值是Runtime.getRuntime().availableProcessors() -1。如果你在一個雙核的機器上通過Arrays.parallelSort()來運行并行排序的話,默認使用的是普通的Arrays.sort()方法。盡管Oracle的官方文檔可能許諾你可以獲得性能提升,但是你在一個雙核的機器上可能完全看不著任何提升。
然而,更大的問題在于Runtime.getRuntime().availableProcessors()也并非都能返回你所期望的數(shù)值。比如說,在我的雙核1-2-1機器上,它返回的是2,這是對的。不過在我的1-4-2機器 上,也就是一個CPU插槽,4核,每個核2個超線程,這樣的話會返回8。不過我其實只有4個核,如果代碼的瓶頸是在CPU這塊的話,我會有7個線程在同時 競爭CPU周期,而不是更合理的4個線程。如果我的瓶頸是在內存這的話,那這個測試我可以獲得7倍的性能提升。
不過這還沒完!Java Champions上的一個哥們發(fā)現(xiàn)了一種情況,他有一臺16-4-2的機器 (也就是16個CPU插槽,每個CPU4個核,每核兩個超線程,返回的值居然是16!從我的i7 Macbook pro上的結果來看,我覺得應該返回的是16*4*2=128。在這臺機器上運行Java 8的話,它只會將通用的FJ池的并發(fā)數(shù)設置成15。正如 Brian Goetz所指出的,“虛擬機其實不清楚什么是處理器,它只是去請求操作系統(tǒng)返回一個值。同樣的,操作系統(tǒng)也不知道怎么回事,它是去問的硬件設備。硬件會告訴它一個值,通常來說是硬件線程數(shù)。操作系統(tǒng)相信硬件說的,而虛擬機又相信操作系統(tǒng)說的?!?/p>
所幸的是還有一個解決方案。啟動的時候,你可以通過系統(tǒng)屬性 java.util.concurrent.ForkJoinPool.common.parallelism來設置通用池的并發(fā)數(shù)。也就是說,我們可以通過-Djava.util.concurrent.ForkJoinPool.common.parallelism=128來啟動這段程序,現(xiàn)在你可以看到它的并發(fā)數(shù)是128了:
import java.util.concurrent.*; public class ForkJoinPoolCommon { public static void main(String... args) { System.out.println(ForkJoinPool.commonPool()); } }
還有兩個控制通用池的額外的系統(tǒng)屬性。如果你希望處理未捕獲異常的話,你可以通過java.util.concurrent.ForkJoinPool.common.exceptionHandler來指定一個處理類。如果你希望有自己的線程工廠的話,可以通過 java.util.concurrent.ForkJoinPool.common.threadFactory來配置。默認的Fork/Join池的工廠生成的是守護線程,可能你的應用里面不希望使用它。不過如果你這么做的話請小心——這樣你就無法關閉這個通用池了。
到此這篇關于詳解Java8與Runtime.getRuntime().availableProcessors()的文章就介紹到這了,更多相關Java8與Runtime.getRuntime().availableProcessors()內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用Servlet Filter實現(xiàn)系統(tǒng)登錄權限
這篇文章主要為大家詳細介紹了使用Servlet Filter實現(xiàn)系統(tǒng)登錄權限,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-10-10SpringBoot通過@Value實現(xiàn)給靜態(tài)變量注入值詳解
這篇文章主要介紹了springboot如何通過@Value給靜態(tài)變量注入值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07Java面試??贾瓹oncurrentHashMap多線程擴容機制詳解
幾乎所有的后端技術面試官都要在?ConcurrentHashMap?技術的使用和原理方面對小伙伴們進行刁難,本文主要來和大家聊聊ConcurrentHashMap多線程的擴容機制,希望對大家有所幫助2023-05-05淺析springboot通過面向接口編程對控制反轉IOC的理解
這篇文章主要介紹了springboot通過面向接口編程對控制反轉IOC的理解,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2020-08-08深度優(yōu)先與廣度優(yōu)先Java實現(xiàn)代碼示例
這篇文章主要介紹了深度優(yōu)先與廣度優(yōu)先Java實現(xiàn)代碼示例,具有一定借鑒價值,需要的朋友可以參考下。2017-12-12SpringBoot項目將mybatis升級為mybatis-plus的方法
本文主要介紹了SpringBoot項目將mybatis升級為mybatis-plus的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-01-01