Java 8已经发布两个月了,我也接到不少公司的邀请希望能给他们做些关于如何使用新语法编程的培训。我问他们什么时候会用Java 8来写生产环境的代码,回答通常都是”还没呢,我们现在只是想了解下“。每当一个新的大版本发布的时候,形成良好的编程习惯总是需要一段时间的。Java 5的泛型出现的时候也是一样。一开始,程序员试尽了各种复杂的方式。我很惭愧,我自己也曾做过了超出了泛型最初的设计本意的一些事情。不过这么多年过去了,我们也开始不那么滥用泛型了,现在通常用它来访问集合,这样会更安全一些。
我相信Java 8也会经历同样的阶段,尤其是一些更酷的特性,比如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池的工厂生成的是守护线程,可能你的应用里面不希望使用它。不过如果你这么做的话请小心——这样你就无法关闭这个通用池了。
当我在写这篇文章的时候,Pierre-yves Saumont 写的那篇“Java 8存在的问题三:流与并行流”出现在了我的收件箱里。他在文中写道:“默认所有的流都使用同样的Fork/Join池,它默认使用的线程数会尽可能地接近所运行的计算机的核数。”然而,如果我们在本文中所看到的,默认数字实际是硬件线程数减1,而不是CPU核数!除此之外,有的时候这个数字是CPU插槽数减1。不过他的确指出了一个问题,也就是一旦这个通用池到达了期望并发数的上限,它就不会再创建新的线程了。尽管可能有很多任务正在等待。相像一下一个重度使用并行流的Java 8应用服务器。它们都共用同一个通用池,这样的话,可能会导致严重的性能瓶颈!
Java 8的普及尚需时日,我也并不急于做一些相关的课程培训。你可能很难想像,不过我有些客户的确是仍在使用Java 1.4.2。甚至还有人在用Java 1.1写代码的。我们看到越来越多一些爱冒险的客户正在逐步迁移到Java 7上。真的挺遗憾的,Java 8有些语法糖我的确是非常喜欢,很快我还会写些文章来分享下我的新发现。
注:其实Java 7也会出现这样的情况,只是Java 8中并行流的一些默认行为可能会让这个问题更明显。
原创文章转载请注明出处:
http://it.deepinmind.com
英文原文链接
分享到:
相关推荐
在Java 8中,`Runtime.getRuntime().availableProcessors()`是一个重要的方法,用于获取当前系统可用的处理器核心数量。这个信息对于优化多线程程序,尤其是使用并行处理的场景至关重要,例如Java 8引入的并行流...
14. getRuntime():返回与当前 Java 应用程序相关的运行时对象。 15. halt(int status):强行终止目前正在运行的 Java 虚拟机。 16. load(String filename):加载作为动态库的指定文件名。 17. loadLibrary(String ...
在Java中,可以使用Runtime.getRuntime()方法来获取当前系统的内存信息。该方法返回一个Runtime对象,该对象提供了一些方法来获取系统的内存信息,例如maxMemory()、totalMemory()、freeMemory()等。 maxMemory()...
3. **运行时信息**:`Runtime.getRuntime()`返回一个`Runtime`对象,用于与Java虚拟机进行交互。你可以通过它来获取CPU核心数、总内存、剩余内存等信息。例如,`Runtime.getRuntime().availableProcessors()`返回...
对于`SocketConnector`和`SocketAcceptor`,I/O Processor线程数量由`Runtime.getRuntime().availableProcessors() + 1`决定,这意味着JVM会根据系统的CPU核心数动态调整。然而,无论这个值如何,Acceptor和...
Java的Runtime类是Java语言与运行环境之间的一个接口,它提供了很多方法来管理Java应用程序的运行环境,包括内存管理和执行外部程序等。Runtime类的实例是全局的,每个Java应用程序都只有一个Runtime实例,通过`...
Runtime rt = Runtime.getRuntime(); long totalMemory = rt.totalMemory(); long freeMemory = rt.freeMemory(); System.out.println("总内存大小: " + totalMemory / 1024 + " KB"); System.out.println("空闲...
*java.lang.Runtime.getRuntime().availableProcessors; [success speedup poolSizeVector] = parallelWorkerSpeedTest_mdcs( ' enableFigures ' , 1 , ' numPlayersList ' ,maxParallelJobsToTest, ' numHandsList ...
executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * POOL_SIZE); logger.info("端口号为" + port + "的服务器启动"); } catch (IOException e) { e.printStackTrace...
在Java中,可以使用`java.lang.Runtime.getRuntime().availableProcessors()`获取当前系统的CPU核心数,作为估算线程池大小的基础。此外,还可以通过监控和压力测试,结合实际工作负载情况,逐步调整线程池大小,...
默认情况下,ForkJoinPool的大小与系统的处理器核心数相等,可以通过`Runtime.getRuntime().availableProcessors()`获取,或者通过`java.util.concurrent.ForkJoinPool.common.parallelism`属性设置。 并行流的性能...
通过`Runtime.getRuntime().availableProcessors()`获取系统可用的CPU核心数,这在决定线程数量时非常有用。通常,创建与CPU核心数相等的线程可以最大化硬件资源的利用率。然而,过多的线程可能会导致上下文切换...
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());项目中支持将本地路径中的html文件批量转换为PDF,放入本地文件系统中。使用说明List htmlsPaths = ...
例如,在使用 ThreadPoolExecutor 创建线程池时,可以使用 Runtime.getRuntime().availableProcessors() 方法来获取 CPU 的核心数,然后将其设置为线程池的最大线程数。 代码示例: ``` ThreadPoolExecutor ...
基于java tcp socket通信的拆包和装包源码 个人笔记整理 - Shadow Chapter01 - Netty简单使用 ...NioEventLoopGroup(Runtime.getRuntime().availableProcessors()); // 服务端启动器 ServerBoots
在 Java 8 中,parallelStream 的线程池个数默认为 Runtime.getRuntime().availableProcessors() - 1。这意味着,parallelStream 将使用除了一个处理器核心以外的所有处理器核心来处理数据。 结论 在本篇文章中,...
用 int threadNum = Runtime.getRuntime().availableProcessors();得到你的cpu内核数 2.1对于内核数这个来做下自己的说明,当时自己在做的时候,查看了一些对于使用cpu核数的文章 有些高手做了一些性能方面的...
4. **Runtime.getRuntime().availableProcessors()**:这个方法返回运行Java虚拟机的系统的处理器核心数,用于确定线程池的大小,确保线程池的规模与硬件资源相匹配。 5. **ServerSocket.accept()**:这个方法在...
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); public ImageLoader() { initImageCache(); } private void initImageCache() { final ...