【问题标题】:Java 8 Collections concurrent processingJava 8 Collections 并发处理
【发布时间】:2014-03-14 05:43:23
【问题描述】:

我计划在我的公司就 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. 但为了并行化上述迭代,必须显式使用Stream APIparallel()方法

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

    但即使那样,也不能保证操作会并行完成,因为parallelStream() 的 Javadoc 说以下"Returns a possibly parallel {@code Stream} with this collection as its source. It is allowable for this method to return a sequential stream"

  6. 最终,无法保证所有内核都会被利用,因为线程调度不是 JVM 的职责,而是由操作系统决定的。

编辑

我最难正确理解第 5 点和第 6 点。正如各种 Java 8 博客所说,“使用这个新的 parallelStream(),您将获得开箱即用的并行处理(免费,作为应用程序程序员,您不必担心这一点)”,我一句话的问题应该是那真的一直都是正确的吗?

【问题讨论】:

  • JVM 线程不一定映射到 OS 线程。 JVM 完全自己做一些线程调度。
  • 这一切似乎都没有问题;要求对小论文提供一般性反馈并不适合 SO 格式。
  • 我认为 JVM 使用了一些本机操作系统级别的能力来实现多线程,但只有这些能力非常依赖于操作系统
  • @BobbyDigital 我认为您没有理解这种变化的主要意义:转换后收集处理可以自动并行化。这不仅是一个巨大的胜利,而且实际上也是将 lambdas 引入 Java 的原因。他们需要一个非常强大的杀手级功能来推动他们的发展。
  • @BobbyDigital 不,这次不是。就个人而言,我认为这种变化非常令人兴奋。它不仅会改变 Java 代码的外观(这是泛型所做的唯一事情),还会改变其基本的语义,而且这种改变正朝着如此伟大而重要的方向发展。并行性每天都在变得越来越热,当语言调整伴随着这个新要求时,Java 就在那儿。

标签: java multithreading concurrency parallel-processing java-8


【解决方案1】:

其他答案基本正确。但是,Java8 中的并行特性是基于 Fork/Join 框架的。其中的 Join() 是行不通的,所以它被 CountedCompleter 取代。正如我在this article 中所写的那样,该课程也存在缺陷

其他答案中经常提到的“依赖”也适用于此。

【讨论】:

    【解决方案2】:

    感谢您对以下几点提供反馈 (真/假)..

    不幸的是,没有一个答案是对的或错的。它们都是“取决于”或“很复杂”。 :-)

    1:线程是操作系统中最低的调度单位。

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

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

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

    另一种构建并行应用程序的方法是使用数据并行。 Java SE 7 引入了 Fork-Join 框架。这指的不是 threads 而是 tasks 的分叉和连接,具体来说,代表数据的递归可拆分部分的任务。 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:但是为了使上述迭代并行化,必须显式使用 Stream API 的 parallel 方法....但即使这样也不能保证操作会并行完成。

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

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

    6:最终,不能保证所有内核都会被利用,因为线程调度不是 JVM 的责任,而是由操作系统决定的。 ...正如各种 Java 8 博客所说,“使用这个新的 parallelStream(),您将获得开箱即用的并行处理(免费,作为应用程序程序员的您不必担心这个)” , 我的问题是一句话真的一直都正确吗?

    如上所述,不保证。系统中有许多层可以左转。即使您的普通 FJ 池的线程数与内核数一样多,也不能保证每个 Java 线程都有自己的操作系统线程。 (在 Hotspot JVM 中,我认为这始终是正确的。这取决于 JVM。)同一系统上可能有其他进程——甚至是其他 JVM——竞争内核,因此您的应用程序可能不会获得尽可能多的内核你想要。从这个意义上说,JVM 任由操作系统为其调度线程。

    我不确定该博客条目的出处,但是关于“免费”并行处理和“您不必担心”的观点被夸大了。其实基本上是错的。

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

    即使您已经设法避免了这些陷阱,但在 N 核系统上运行并行流并不会给您带来 N 倍的加速。它只是不那样工作。对于小型工作负载,拆分和加入并行任务的开销占主导地位,这可能导致计算比顺序计算慢。对于较大的工作负载,开销被并行加速所抵消,但开销仍然存在。加速程度还取决于工作负载的性质、拆分特征、数据的块状等。调整并行应用程序是一种魔法。

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

    对于不太容易并行化的工作负载,您可能需要对应用程序进行大量思考和重组,然后才能尝试并行运行它。

    我不会说 Java 8 为您提供“免费”或“无忧”或类似的并行性。我说 Java 8 让您有机会比以前更方便地编写并行程序。但您仍需努力使其正确,而且您可能仍需努力实现所需的加速。

    【讨论】:

    • 这是我所希望的尽可能多的细节。它回答了我的疑问,也是我进一步研究的一个很好的起点。谢谢。
    【解决方案3】:

    这真的一直都是正确的吗?

    它在你想要的任何时候都是正确的。顺序流也可以的特殊津贴对于拥有这个有用的功能是绝对必要的:在许多情况下(可能是测试、调试等),您将需要一个简单的顺序流。大多数并发问题通过尝试在非并发设置中重现问题来开始解决问题。并发调试要困难得多,首先要确定它是真的需要。

    您永远不必担心 CPU 内核利用率:这是一种古老而稳定的技术,它们确实在我使用 Java 的所有经验中都得到了利用。如果您在 CPU 利用率仪表板上遗漏了某个百分比,您几乎可以肯定这些问题是可以通过简化锁和其他线程协调来解决的Java,而不是完全正确的 Java 程序成为受害者运行时的怪癖。

    【讨论】:

    • 谢谢(+1)。我理解您关于保持顺序流的论点。如果我不必担心 CPU 核心利用率 - 是因为 Java 8 中的 JVM 足够智能还是因为操作系统足够智能?另一个悬而未决的问题是parallelStream() 是否仍然保证并发处理,还是只是再次依赖于实现?
    • Another question that goes unanswered---我的完整致力于准确回答这部分,所以我被评论弄糊涂了。
    • 关于核心利用率:长期以来,JVM 一直“足够聪明”。当然,两者 JVM 和操作系统都必须没有问题,最终结果是良好的 CPU 利用率。
    • It is correct all the time you want it to be --> 所以你的意思是说我现在理解的就是这种情况。
    • 我的意思是,当你希望你的程序具有并行性时,它就会有。当你不想要它时,它不会。这难道不是你所希望的吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多