详解Java8与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,这段代码就会失败了。

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池的工厂生成的是守护线程,可能你的应用里面不希望使用它。不过如果你这么做的话请小心——这样你就无法关闭这个通用池了。

到此这篇关于详解Java8与Runtime.getRuntime().availableProcessors()的文章就介绍到这了,更多相关Java8与Runtime.getRuntime().availableProcessors()内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java8 LocalDate LocalDateTime等时间类用法实例分析

    本文实例讲述了java8 LocalDate LocalDateTime等时间类用法.分享给大家供大家参考,具体如下: 这篇文章主要是java8中新的Date和Time API的实战.新的Date和Time类是Java开发者社区千呼万唤始出来的.Java8 之前存在的Date类一直都受人诟病,很多人都会选择使用第三方的date库joda-time.Java8中的date和time api是jodatime的作者参与开发的,实现了JSR310的全部内容.这些新的api都在包java.time下.

  • Java 8跳过本次循环,继续执行以及跳出循环,终止循环的代码实例

    在Java8之前,最开始使用for i 循环,很老旧,后来有了高级的for each 循环,然后这个跳出本次循环和跳出所有的for循环,都简单,稍微没见过的就是跳出多层for循环. 然后就是Java8出的foreach循环,这个循环里面,break和continue都不管用啦.需要使用return,这个只能跳过本次循环,还是会继续执行for循环的,那么怎么跳出这个Java8的foreach循环呢? 下面对所有的循环,都来了一次操作. 看看如何,跳出当前循环,继续执行:或者直接跳出for循环:或者

  • 详解Java8 Collect收集Stream的方法

    Collection, Collections, collect, Collector, Collectos Collection是Java集合的祖先接口. Collections是java.util包下的一个工具类,内涵各种处理集合的静态方法. java.util.stream.Stream#collect(java.util.stream.Collector<? super T,A,R>)是Stream的一个函数,负责收集流. java.util.stream.Collector 是一个收

  • 深入了解java8的foreach循环

    虽然java8出来很久了,但是之前用的一直也不多,最近正好学习了java8,推荐一本书还是不错的<写给大忙人看的javase8>.因为学习了Java8,所以只要能用到的地方都会去用,尤其是Java8的Stream,感觉用起来觉得很方便,因为点点点就出来了,而且代码那么简洁.现在开始慢慢深入了解java8,发现很多东西不能看表面. 比如常规遍历一个集合,下面给出例子: 1.首先遍历一个List 方式1.一开始是这样的: public static void test1(List<Strin

  • Java8的default方法详细介绍

    什么是default方法? Java 8发布以后,可以给接口添加新方法,但是,接口仍然可以和它的实现类保持兼容.这非常重要,因为你开发的类库可能正在被多个开发者广泛的使用着.而Java 8之前,在类库中发布了一个接口以后,如果在接口中添加一个新方法,那些实现了这个接口的应用使用新版本的接口就会有崩溃的危险. 有了Java 8,是不是就没有这种危险了?答案是否定的. 给接口添加default方法可能会让某些实现类不可用. 首先,让我们看下default方法的细节. 在Java 8中,接口中的方法可

  • Java8 CompletableFuture详解

    Java 8来了,是时候学一下新的东西了.Java 7和Java 6只不过是稍作修改的版本,而Java 8将会发生重大的改进.或许是Java 8太大了吧?今天我会给你彻底地解释JDK 8中的新的抽象 – CompletableFuture.众所周知,Java 8不到一年就会发布,因此这篇文章是基于JDK 8 build 88 with lambda support的.CompletableFuture extends Future提供了方法,一元操作符和促进异步性以及事件驱动编程模型,它并不止步

  • JAVA8 十大新特性详解

    "Java is still not dead-and people are starting to figure that out." 本教程将用带注释的简单代码来描述新特性,你将看不到大片吓人的文字. 一.接口的默认方法 Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下: 复制代码 代码如下: interface Formula {    double calculate(int a); default do

  • Java8中利用stream对map集合进行过滤的方法

    前言 Stream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找.遍历.过滤以及常见计算等. 最近公司在大张旗鼓的进行代码审核,从中也发现自己写代码的不好习惯.一次无意的点到了公司封装的对map集合过滤的方法,发现了stream.于是研究了一下.并对原有的代码再次结合Optional进行重构下 原有方法说明 主要处理过滤条件Map对象,过滤掉了null和空字符串 等操作 这

  • 详解Java8与Runtime.getRuntime().availableProcessors()

    lambda表达式以及并行流.官方承诺你写出来的代码更运行得更快.流会自动通过Fork/Join池并行地执行.我听过一些关于Java 8的主题的演讲,不过在这个非常关键的点上它们都说的有点问题.我计划在后续的文章中对并行流进行下深入的讲解,在这之前我先花点时间仔细地分析下它.关于这个问题,我只想问你们一个非常简单的问题,不过也是一个非常重要的问题,因为它是很多问题的关键所在.这个问题是: 这些并行操作的线程都是从哪来的? 在Java 8里,我们有一个通用的Fork/Join池,我们可以通过For

  • 详解JAVA8 函数式接口

    写在前面 Java8中内置了一些在开发中常用的函数式接口,极大的提高了我们的开发效率.那么,问题来了,你知道都有哪些函数式接口吗? 函数式接口总览 这里,我使用表格的形式来简单说明下Java8中提供的函数式接口. 四大核心函数式接口 首先,我们来看四大核心函数式接口,如下所示. 函数式接口 参数类型 返回类型 使用场景 Consumer消费型接口 T void 对类型为T的对象应用操作,接口定义的方法:void accept(T t) Supplier供给型接口 无 T 返回类型为T的对象,接口

  • 详解Java8中的Lambda表达式

    Lambda是什么 Lambda表达式,也可称为闭包,是java8的新特性,作用是取代大部分内部类,优化java代码结构,让代码变得更加简洁紧凑. Lambda的基本语法 (expression)->expression 或 (expression)->{statements;} Lambda最重要特点 用()->{}代码块替代匿名内部类 //(param)->expression;//(param)->statment;//(param)->{statments};/

  • 详解Java8合并两个Map中元素的正确姿势

     1. 介绍 本入门教程将介绍Java8中如何合并两个map. 更具体说来,我们将研究不同的合并方案,包括Map含有重复元素的情况. 2. 初始化 我们定义两个map实例 private static Map<String, Employee> map1 = new HashMap<>(); private static Map<String, Employee> map2 = new HashMap<>(); Employee类 public class

  • 详解Java8的forEach(...)如何提供index值

    Java2遍历集合 遍历Collection的代码,可以是采用Iterator接口,通过next()遍历.如: List<String> list = Arrays.asList("Hi", "I", "am", "Henry.Yao"); // 此处已经用到了泛型,不能算是纯粹的Java2代码,仅作Iterator示范 for (Iterator<String> it = list.iterator(

  • 详解Java8的groupBy实现集合的分组

    场景: Java8的groupBy实现集合的分组,类似Mysql的group by分组功能,注意得到的是一个map 1.对集合按照单个属性分组 e.g. 按照skuId分组 Map<String, List<EntryDeliveryDetailywk>> detailsMap01 = dtos1.stream() .collect(Collectors.groupingBy(EntryDeliveryDetailywk::getskuId)); 2对集合按照多个属性分组 solu

  • 详解Java8如何使用Lambda表达式进行比较

    目录 支持Lambda的基本排序 无类型定义的基本排序 使用引用静态方法进行排序 Sort Extracted Comparators 反向排序 使用多个条件进行排序 使用多个条件排序-组合 使用Stream.sorted()对列表进行排序 使用Stream.sorted()对列表进行反向排序 Null值 结论 在Java 8之前,对集合进行排序需要为排序中使用的比较器 Comparator 创建一个匿名内部类: new Comparator<Human>() { @Override publ

  • 详解Java8 CompletableFuture的并行处理用法

    目录 前言 场景 用法 1.在线API 2.编写在线API查询 3.编写查询服务 4.编写测试接口 5.效果 6.CompletableFuture并行查询 7.编写测试接口 8.CompletableFuture效果 思考 前言 工作中你可能会遇到很多这样的场景,一个接口,要从其他几个service调用查询方法,分别获取到需要的值之后再封装数据返回. 还可能在微服务中遇到类似的情况,某个服务的接口,要使用好几次feign去调用其他服务的方法获取数据,最后拿到想要的值并封装返回给前端. 这样的场

  • 详解Java8中Optional的常见用法

    目录 一. 简介 二.Java8 之前,空指针异常判断 三.Optional的使用 1.创建Optional实例 2.访问 Optional 对象的值 3.返回默认值 4.返回异常 (常用) 5.转换值 6.过滤值 一. 简介 Opitonal是java8引入的一个新类,目的是为了解决空指针异常问题.本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空. Optional 是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现.但是 Optional

随机推荐