我是一个出色的多任务处理者。 即使我在写这篇文章,我仍然可以为昨天在一个大家都对我陌生的聚会上发表的言论感到尴尬。 好消息是,我并不孤单– Java 8在多任务处理方面也相当出色。 让我们看看如何。
Java 8中引入的关键新功能之一是并行数组操作。 这包括使用自动利用多核体系结构的Lambda表达式对项目进行排序,过滤和分组的功能。 作为Java开发人员,这里的承诺是在我们付出最小努力的情况下立即获得性能提升。 很酷
因此,问题就变成了:这东西有多快?何时使用? 好吧,快速的答案令人遗憾- 这取决于 。 想知道什么? 继续阅读。
新的API
新的Java 8并行操作API非常漂亮。 让我们看一下我们将要测试的一些。
- 要使用多个核对数组进行排序 ,您要做的就是–
Arrays.parallelSort(numbers);
- A 组收集到不同的组基于特定标准(如黄金和非素数) -
Map<Boolean, List<Integer>> groupByPrimary = numbers.parallelStream().collect(Collectors.groupingBy(s -> Utility.isPrime(s)));
- 要过滤掉值,您要做的就是–
Integer[] prims = numbers.parallelStream().filter(s -> Utility.isPrime(s)).toArray();
- 要使用多个核对数组进行排序 ,您要做的就是–
将此与自己编写多线程实现进行比较。 大大提高了生产力! 我个人对这种新体系结构喜欢的东西是Spliterators的新概念,用于将目标集合拆分为多个块,然后可以并行处理这些块并缝合回去。 就像他们的老兄迭代器用于遍历项目集合一样,这是一种灵活的体系结构,使您可以编写自定义行为来遍历和拆分可以直接插入的集合。
那么它的表现如何呢?
为了验证这一点,我检查了并行操作在两种情况下( 低和高竞争情况)如何工作。 原因是本身运行多核算法通常会产生不错的结果。 当踢脚程序开始在实际服务器环境中运行时,它就会进入。 那就是大量池线程不断争夺宝贵的CPU周期来处理消息或用户请求的地方。 这就是事情开始放缓的地方。 为此,我设置了以下测试 。 我将100K个整数的数组随机化,其值范围在零到一百万之间。 然后,我使用传统的顺序方法和新的Java 8并行API对它们进行排序,分组和过滤操作。 结果并不令人惊讶。
- Quicksort现在快了4.7倍。
- 分组现在快5倍倍。
- 现在, 过滤速度提高了5.5倍。
一个快乐的结局? 不幸的是没有 。
*结果与运行100次的其他测试一致。*测试机器为MBP i7四核。
那么在负载下会发生什么呢?
到目前为止,情况一直很糟糕,原因是在CPU周期的线程之间几乎没有争用。 这是一种理想的情况,但是不幸的是,在现实生活中这种情况很少发生。 为了模拟一个与您在现实环境中通常看到的场景更相像的场景,我设置了第二个测试。 该测试运行相同的算法集,但是这次在十个并发线程上执行它们,以模拟在服务器承受压力( 将其命名为Kermit! )时处理服务器执行的十个并发请求。 然后,将使用传统方法或新的Java 8 API依次处理每个请求。
结果
- 现在排序速度仅加快了20%, 下降了 23倍。
- 现在, 过滤速度仅提高了20%, 下降了25倍 。
- 现在, 分组 速度降低了15% 。
较高的规模和竞争水平很可能会使这些数字进一步下降。 原因是在已经是多线程环境的环境中添加线程对您没有帮助。 我们只有多少个CPU,而不是线程。
结论
尽管这些都是非常强大且易于使用的API,但它们并不是灵丹妙药。 我们仍然需要对何时使用它们做出判断。 如果事先知道您将并行执行多个处理操作,那么考虑使用排队体系结构将并行操作的数量与您可用的实际处理器数量相匹配可能是个好主意。 这里最困难的部分是运行时性能将取决于实际的硬件体系结构和压力级别。 您的代码很可能只会在负载测试或生产过程中看到这些代码,这使这种经典情况“易于编码,难以调试”。
翻译自: https://www.javacodegeeks.com/2014/04/new-parallelism-apis-in-java-8-behind-the-glitz-and-glamour.html