Java 8 Collections并发处理

sen*_*iwu 20 java parallel-processing concurrency multithreading java-8

我计划在我的公司内部进行Java 8中的新功能和概念的内部演示.

我想关注的是新集合库的并行处理功能.

无论我在哪里阅读Java 8以及对集合库的更多功能样式迭代器的需求,都会提到这将有助于利用当前正常的多核服务器.但很少有人提到这是如何实现的,以及这是否是一个普遍的事实,更不用说任何有关性能的基准.

正如我公司中经验丰富的开发人员声称了解线程并不知道实际线程如何在较低级别工作,我正在尝试收集这方面的一些知识.基于阅读几篇博客等,我做了一系列以下断言.

我会感谢以下几点的反馈(真/假) ..

  1. 线程是操作系统中最低的调度单位(是基本的东西,但不是所有应用程序员都知道这个;-))

  2. 单线程程序一次只能在一个核心上运行.因此,在四核CPU中,例如不使用75%的CPU.

  3. 现有Java集合迭代器的问题在于它是一个外部迭代器,并且不可能(至少开箱即用)将庞大的集合迭代分发给多个线程.新的集合库操作使得可以在不需要处理低级并发问题的情况下实现并发

  4. Java 8使用增强的集合库可以使用内部迭代器并行化迭代

    而不是Java 7

    for (Shape s : shapes) {if (s.getColor() == RED)s.setColor(BLUE); }

    我们有Java 8

    shapes.forEach(s -> { if (s.getColor() == RED) s.setColor(BLUE); })

  5. 但为了平行上述迭代,必须明确使用parallel()方法Stream API

    private static void printUsingCoolLambda (final List<String> names) { names.parallelStream().forEach(s -> System.out.println(s)); System.out.println("Printed using printUsingCoolLambda"); }

    但即便如此,也不能保证操作将并行完成,因为Javadoc parallelStream()说下面的"返回一个可能并行的{@code Stream},并将此集合作为其源.此方法允许返回顺序流"

  6. 最终,无法保证所有核心都将被利用,因为线程调度不是JVM的责任,而是由OS决定.

编辑

我最难得到第5点和第6点.正如各种Java 8博客所说的那样"使用这个新的parallelStream()并且您将获得开箱即用的并行处理(免费,并且您作为应用程序员免于担心这一点)",我的问题用一句话本来应该是真的正确吗?

Stu*_*rks 28

我会感谢以下几点的反馈(真/假)..

不幸的是,答案都不是真或假.它们都是"它依赖"或"它很复杂".:-)

1:线程是OS中最低的调度单位.

这基本上是正确的.操作系统调度线程,并且大多数情况下,Java线程对应于OS线程.

然而,故事还有更多内容.我鼓励你不要过多考虑线程.它们是用于构建并行应用程序的非常低级的构造.

当然可以使用线程编写应用程序,但通常更喜欢使用更高级别的构造.一个这样的构造是一个任务,它是一个特定于应用程序的工作块.如果您可以将工作负载划分为单独的任务,则可以将这些任务提交给Executor,Executor将管理线程上任务的调度以及线程的创建和销毁.这是java.util.concurrentJava SE 5中的内容.

构建并行应用程序的另一种方法是使用数据并行.Java SE 7引入了Fork-Join框架.这指的是分叉而不是线程而不是任务,特别是表示递归可分割数据部分的任务.FJ框架对于某些工作负载非常有效,但是任务的分离和加入是程序员的责任,这可能是繁重的.

Java SE 8中的新功能是流API,它以更方便的方式支持数据并行.

我从你关于线程的问题中推断了很多,但是你的问题似乎集中在线程上,并且并行性比线程要多得多.(我的一位同事最近说过,"线程是假神.")

2:单线程程序一次只能在一个核心上运行.因此,在四核CPU中,例如不使用75%的CPU.

大部分都是如此.如果只考虑应用程序线程,单个线程永远不会使用超过25%的四核CPU.但是,如果考虑在JVM中运行Java线程,即使单线程Java应用程序在多核系统上运行的速度也可能比在单核系统上运行得快.原因是像垃圾收集器这样的JVM服务线程可以与多核系统上的应用程序线程并行运行,而它们必须抢占单核系统上的应用程序线程.

3:现有Java集合迭代器的问题在于它是一个外部迭代器,并且不可能(至少开箱即用)将庞大的集合迭代分发到多个线程.新的集合库操作使得可以在不需要处理低级并发问题的情况下实现并发.

大多数是的.外部迭代内部迭代是概念.外部迭代由实际Iterator接口体现.内部迭代可能使用Iterator一个简单的for循环,一组fork-join任务或其他东西.

它不是新的集合库,而是Java 8中的新Streams API将提供一种跨线程分发工作的更方便的方法.

4:Java 8使用增强的集合库可以使用内部迭代器并行化迭代(... shapes.forEach示例...)

关.同样,它是新的Streams库,而不是集合,提供了方便的并行性.没有什么比这更好的了Collection.parallelForEach.要并行处理集合的元素,您必须从中拉出并行流.java.util.Arrays类中的数组也有各种并行操作.

5:但是为了并行化上述迭代,必须明确使用parallelStream API 的方法....但即使这样,也不能保证操作将并行完成.

是的,您需要使用parallelor parallelStream方法请求并行性,具体取决于您是从流还是集合开始.

对于没有保证,当然,生活中从来没有任何保证.:-)毕竟,如果你在单核系统上运行,没有什么可以并行运行.另一种情况是,在applet中,安全管理器可能会禁止应用程序使用多个线程.实际上,在大多数环境中,请求并行流确实会分散工作负载并并行运行任务.默认情况下,这些任务在公共fork-join池中运行,默认情况下,该具有与系统中的核心一样多的线程.但有人可能已将线程数设置为不同的数字,甚至设置为1,这是API本身无法提供任何保证的一个原因.

6:最终,不能保证所有核心都将被利用,因为线程调度不是JVM的责任,而是由OS决定....正如各种Java 8博客所说的那样"使用这个新的parallelStream(),你将获得开箱即用的并行处理(免费,你作为应用程序员免于担心这一点)",我的问题一句话本来就是真的正确吗?

如上所述,没有保证.系统中有许多层可以左转.即使您的公共FJ池具有与核心一样多的线程,也不能保证每个Java线程都有自己的OS线程.(在Hotspot JVM中,我认为这总是正确的.它取决于JVM.)在同一系统上可能有其他进程 - 甚至其他JVM - 竞争核心,因此您的应用程序可能没有像你想.从这个意义上讲,JVM受操作系统的支配,可以为它安排线程.

我不确定博客条目的来源,但是关于并行处理"免费"和"你不必担心"的情绪被夸大了.事实上,这基本上是错误的.

确实,与使用早期的API相比,可以更方便地编写并行流.但也有可能让它变得非常非常错误.如果您将副作用放入流管道中,您将遇到竞争条件,并且每次都可能得到不同的错误答案.或者,即使您注意围绕副作用进行同步,也可能会产生足够的争用,以使并行流的运行速度甚至比顺序流慢.

即使你已经设法避免这些陷阱,但并非如此,在N核系统上运行并行流将使你获得N次加速.它只是不起作用.对于小型工作负载,拆分和连接并行任务的开销占主导地位,这可能导致计算比顺序任务慢.对于较大的工作负载,开销由并行加速来抵消,但开销仍然存在.加速量还取决于工作负载的性质,分裂特性,数据的粗糙等.调整并行应用程序是一种黑色艺术.

对于易于并行化的工作负载,根据我的经验,最大化双核系统非常容易.四核系统通常可以获得至少3倍的加速.拥有更多内核,获得5x-6x加速并不困难,但加速超过这需要实际工作.

对于不那么容易并行化的工作负载,您甚至可以在尝试并行运行之前对应用程序进行大量思考和重构.

我不会说Java 8为您提供"免费"或"无忧"或类似的并行性.我说,Java的8给你编写并行程序比以前更方便的机会.但你仍然需要努力使其正确,你可能仍然需要努力实现你想要的加速.