Java 8與Runtime.getRuntime().availableProcessors()

lambda表達式以及並行流。官方承諾你寫出來的代碼更運行得更快。流會自動經過Fork/Join池並行地執行。我聽過一些關於Java 8的主題的演講,不過在這個很是關鍵的點上它們都說的有點問題。我計劃在後續的文章中對並行流進行下深刻的講解,在這以前我先花點時間仔細地分析下它。關於這個問題,我只想問大家一個很是簡單的問題,不過也是一個很是重要的問題,由於它是不少問題的關鍵所在。這個問題是:
這些並行操做的線程都是從哪來的?

在Java 8裏,咱們有一個通用的Fork/Join池,咱們能夠經過ForkJoinPool.commonPool()來訪問它。並行流,並行排序,CompletableFuture等都會用到它。當你構造一個Fork/Join池的時候,一般你都沒有指定最大線程數。你只是指定了一個指望的併發數,也就是說你但願在運行時的同一時間有多少活躍的線程。當線程被阻塞在一個phaser的時候,會建立另外一個線程來保證池裏有足夠的活躍線程。這個phaser就是觸發這個行爲的同步器。Fork/Join池最大的線程數是32767,但在遠沒達到這個數量時,在大多數操做系統上就會拋出OutOfMemoryError異常了。在這段示例代碼中,我會不斷建立新的RecursiveAction真到達到第一個階段(也就是到達了200個線程)。若是咱們增長到一個更大的數字,好比說到100000,這段代碼就會失敗了。


java

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池沒有一個最大線程數,只有一個指望併發數,這是指咱們但願同時有多少個活躍線程。

通用池是頗有用的,由於它意味着不一樣類型的做業能夠共享同一個池,而不用超出代碼所運行的機器上指望併發數。固然了,若是一個線程因爲非Phaser的其它緣由阻塞了,那可能這個通用池的表現就和預期的不太同樣了。

什麼是通用FJ池的默認的指望併發數?

一般的FJ池的指望併發數的默認值是Runtime.getRuntime().availableProcessors() -1。若是你在一個雙核的機器上經過Arrays.parallelSort()來運行並行排序的話,默認使用的是普通的Arrays.sort()方法。儘管Oracle的官方文檔可能許諾你能夠得到性能提高,可是你在一個雙核的機器上可能徹底看不着任何提高。

然而,更大的問題在於Runtime.getRuntime().availableProcessors()也並不是都能返回你所指望的數值。好比說,在個人雙核1-2-1機器上,它返回的是2,這是對的。不過在個人1-4-2機器 上,也就是一個CPU插槽,4核,每一個核2個超線程,這樣的話會返回8。不過我其實只有4個核,若是代碼的瓶頸是在CPU這塊的話,我會有7個線程在同時 競爭CPU週期,而不是更合理的4個線程。若是個人瓶頸是在內存這的話,那這個測試我能夠得到7倍的性能提高。

不過這還沒完!Java Champions上的一個哥們發現了一種狀況,他有一臺16-4-2的機器 (也就是16個CPU插槽,每一個CPU4個核,每核兩個超線程,返回的值竟然是16!從個人i7 Macbook pro上的結果來看,我以爲應該返回的是16*4*2=128。在這臺機器上運行Java 8的話,它只會將通用的FJ池的併發數設置成15。正如 Brian Goetz所指出的,「虛擬機其實不清楚什麼是處理器,它只是去請求操做系統返回一個值。一樣的,操做系統也不知道怎麼回事,它是去問的硬件設備。硬件會告訴它一個值,一般來講是硬件線程數。操做系統相信硬件說的,而虛擬機又相信操做系統說的。」

所幸的是還有一個解決方案。啓動的時候,你能夠經過系統屬性 java.util.concurrent.ForkJoinPool.common.parallelism來設置通用池的併發數。也就是說,咱們能夠經過-Djava.util.concurrent.ForkJoinPool.common.parallelism=128來啓動這段程序,如今你能夠看到它的併發數是128了:

併發

import java.util.concurrent.*;

public class ForkJoinPoolCommon {
  public static void main(String... args) {
    System.out.println(ForkJoinPool.commonPool());
  }
}
   




還有兩個控制通用池的額外的系統屬性。若是你但願處理未捕獲異常的話,你能夠經過java.util.concurrent.ForkJoinPool.common.exceptionHandler來指定一個處理類。若是你但願有本身的線程工廠的話,能夠經過 java.util.concurrent.ForkJoinPool.common.threadFactory來配置。默認的Fork/Join池的工廠生成的是守護線程,可能你的應用裏面不但願使用它。不過若是你這麼作的話請當心——這樣你就沒法關閉這個通用池了。性能

相關文章
相關標籤/搜索