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池的工厂生成的是守护线程,可能你的应用里面不但愿使用它。不过若是你这么作的话请当心——这样你就没法关闭这个通用池了。性能

相关文章
相关标签/搜索