简体   繁体   English

Java 8 Collections并发处理

[英]Java 8 Collections concurrent processing

I am planning to do an internal presentation in my company on new features and concepts in Java 8. 我计划在我的公司内部进行Java 8中的新功能和概念的内部演示。

What I would like to focus is on the parallel processing capabilities of new collection libraries. 我想关注的是新集合库的并行处理功能。

Wherever I read about Java 8 and the need for more functional style iterators of the collection library, it is mentioned that this will help to leverage the multi-core servers that are normal nowadays. 无论我在哪里阅读有关Java 8以及对集合库的更多功能样式迭代器的需求,都会提到这将有助于利用当前正常的多核服务器。 But very rarely it is mentioned how this is made possible and whether this is a universal truth , let alone any benchmarks on performance. 但很少有人提到这是如何实现的,以及这是否是一个普遍的事实 ,更不用说任何有关性能的基准。

As even experienced developers in my company who claims to know about threads have no clue how actual threading works at the lower level, I am trying to collect some knowledge in this area. 正如我公司中经验丰富的开发人员声称了解线程并不知道实际线程如何在较低级别工作,我正在尝试收集这方面的一些知识。 I have made a set of following assertions based on reading several blogs etc. 基于阅读几篇博客等,我做了一系列以下断言。

I would be thankful for some feedback for the following points (true/false) .. 我会感谢以下几点的反馈(真/假) ..

  1. A thread is the lowest unit of scheduling in an OS (yeah basic stuff, but not all application programmers know this ;-)) 线程是操作系统中最低的调度单位(是基本的东西,但不是所有应用程序员都知道这个;-))

  2. A single threaded program can run only on one core at a time. 单线程程序一次只能在一个核心上运行。 So in a quad core CPU, 75% of the CPU is not utilized for example. 因此,在四核CPU中,例如不使用75%的CPU。

  3. The problem with present Java collection iterator is that it is an external iterator and it is not possible (atleast out of the box) to distribute a bulky collection iteration to multiple threads. 现有Java集合迭代器的问题在于它是一个外部迭代器,并且不可能(至少开箱即用)将庞大的集合迭代分发给多个线程。 The new collection library operations makes it possible to have concurrency without having the need to deal with low level concurrency issues 新的集合库操作使得可以在不需要处理低级并发问题的情况下实现并发

  4. Java 8 makes it possible using an enhanced collection library to parallellize iteration using an internal iterator Java 8使用增强的集合库可以使用内部迭代器并行化迭代

    Instead of Java 7 而不是Java 7

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

    we have in Java 8 我们有Java 8

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

  5. But inorder to parallellize the above iteration, one must explicitly use parallel() method of Stream API 但是为了平行上述迭代,必须明确使用Stream API parallel()方法

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

    But even then there is no guarantee that the operation will be done in parallel as the Javadoc of parallelStream() says the following "Returns a possibly parallel {@code Stream} with this collection as its source. It is allowable for this method to return a sequential stream" 但是即便如此,也不能保证操作将并行完成,因为parallelStream()的Javadoc说下面的“返回一个可能并行的{@code Stream},以此集合为源。这个方法允许返回顺序流“

  6. Ultimately, there is no guarantee that all the cores will be utilized as thread scheduling is NOT a JVM responsibility, rather dictated by OS. 最终,无法保证所有核心都将被利用,因为线程调度不是JVM的责任,而是由OS决定。

edit 编辑

I have most difficulty in getting points 5 and 6 right. 我最难得到第5点和第6点。 As various Java 8 blogs says just that "use this new parallelStream() and you will get parallel processing out of the box(for free and you as an application programmer are freed from having to worry about that)" , my question in one sentence would have been is that really correct all the time ? 正如各种Java 8博客所说的那样“使用这个新的parallelStream()并且您将获得开箱即用的并行处理(免费,并且您作为应用程序员免于担心这一点)” ,我的问题用一句话本来应该是真的正确吗?

I would be thankful for some feedback for the following points (true/false).. 我会感谢以下几点的反馈(真/假)..

Unfortunately none of the answers are either true or false. 不幸的是,答案都不是真或假。 They are all "it depends" or "it's complicated". 它们都是“它依赖”或“它很复杂”。 :-) :-)

1: A thread is the lowest unit of scheduling in an OS. 1:线程是OS中最低的调度单位。

This is basically true. 这基本上是正确的。 OSes schedule threads, and for the most part a Java thread corresponds to an OS thread. 操作系统调度线程,并且大多数情况下,Java线程对应于OS线程。

However, there's more to the story. 然而,故事还有更多内容。 I'd encourage you not to think too much about threads. 我鼓励你不要过多考虑线程。 They are a very low-level construct to use to structure a parallel application. 它们是用于构建并行应用程序的非常低级的构造。

Of course it's possible to write applications using threads, but it's often preferable to use a higher level construct. 当然可以使用线程编写应用程序,但通常更喜欢使用更高级别的构造。 One such construct is a task , which is an application-specific chunk of work. 一个这样的构造是一个任务 ,它是一个特定于应用程序的工作块。 If you can divide your workload into separate tasks, you can submit these tasks to an Executor , which will manage the scheduling of tasks onto threads and the creation and destruction of threads. 如果您可以将工作负载划分为单独的任务,则可以将这些任务提交给ExecutorExecutor将管理线程上任务的调度以及线程的创建和销毁。 This is the java.util.concurrent stuff that went into Java SE 5. 这是Java SE 5中的java.util.concurrent内容。

Another way to structure a parallel applications is using data parallelism . 构建并行应用程序的另一种方法是使用数据并行 Java SE 7 introduced the Fork-Join framework. Java SE 7引入了Fork-Join框架。 This refers to forking and joining not of threads but of tasks , specifically, tasks representing recursively-splittable portions of data. 这指的是分叉而不是线程而不是任务 ,特别是表示递归可分割数据部分的任务。 The FJ framework is quite effective for some workloads, but the splitting and joining of tasks is the responsibility of the programmer, and this can be burdensome. FJ框架对于某些工作负载非常有效,但是任务的分离和加入是程序员的责任,这可能是繁重的。

New in Java SE 8 is the streams API, which supports data parallelism in a much more convenient fashion. Java SE 8中的新功能是流API,它以更方便的方式支持数据并行。

I've extrapolated quite a bit from your question about threads, but your questions seemed focused on threads, and there is much more to parallelism than threads. 我从你关于线程的问题中推断了很多,但是你的问题似乎集中在线程上,并且并行性比线程要多得多。 (One of my colleagues recently said, "Threads are a false God.") (我的一位同事最近说过,“线程是假神。”)

2: A single threaded program can run only on one core at a time. 2:单线程程序一次只能在一个核心上运行。 So in a quad core CPU, 75% of the CPU is not utilized for example. 因此,在四核CPU中,例如不使用75%的CPU。

Mostly true. 大部分都是如此。 If you consider just the application thread, a single thread can never use more than 25% of a quad core CPU. 如果只考虑应用程序线程,单个线程永远不会使用超过25%的四核CPU。 However, if you consider a Java thread running in a JVM, even a single-threaded Java application will likely run faster on a multi-core system than on a single-core system. 但是,如果考虑在JVM中运行Java线程,即使单线程Java应用程序在多核系统上运行的速度也可能比在单核系统上运行得快。 The reason is that JVM service threads like the garbage collector can run in parallel with the application thread on a multi-core system, whereas they have to pre-empt the application thread on a single-core system. 原因是像垃圾收集器这样的JVM服务线程可以与多核系统上的应用程序线程并行运行,而它们必须抢占单核系统上的应用程序线程。

3: The problem with present Java collection iterator is that it is an external iterator and it is not possible (at least out of the box) to distribute a bulky collection iteration to multiple threads. 3:现有Java集合迭代器的问题在于它是一个外部迭代器,并且不可能(至少开箱即用)将庞大的集合迭代分发给多个线程。 The new collection library operations makes it possible to have concurrency without having the need to deal with low level concurrency issues. 新的集合库操作使得可以在不需要处理低级并发问题的情况下实现并发。

Mostly yes. 大多数是的。 External iteration and internal iteration are concepts. 外部迭代内部迭代是概念。 External iteration is embodied by the actual Iterator interface. 外部迭代由实际的Iterator接口体现。 Internal iteration might use an Iterator , a simple for-loop, a set of fork-join tasks, or something else. 内部迭代可能使用Iterator ,简单的for循环,一组fork-join任务或其他东西。

It's not so much the new collection library, but the new Streams API in Java 8 will provide a much more convenient way to distribute work across threads. 它不是新的集合库,而是Java 8中的新Streams API将提供一种跨线程分发工作的更方便的方法。

4: Java 8 makes it possible using an enhanced collection library to parallellize iteration using an internal iterator (... shapes.forEach example ...) 4:Java 8使用增强的集合库可以使用内部迭代器并行化迭代(... shapes.forEach示例...)

Close. 关。 Again, it's the new Streams library, not collections, that provides convenient parallelism. 同样,它是新的Streams库,而不是集合,提供了方便的并行性。 There's nothing like Collection.parallelForEach . 没有像Collection.parallelForEach那样的东西。 To process elements of a collection in parallel, you have to pull a parallel stream from it. 要并行处理集合的元素,您必须从中拉出并行流。 There are also a variety of parallel operations for arrays in the java.util.Arrays class. java.util.Arrays类中的数组也有各种并行操作。

5: But in order to parallelize the above iteration, one must explicitly use the parallel method of the Stream API .... But even then there is no guarantee that the operation will be done in parallel. 5:但是为了并行化上述迭代,必须明确使用Stream API的parallel方法....但即使这样,也不能保证操作将并行完成。

Right, you need to request parallelism with the parallel or parallelStream method, depending on whether you're starting with a stream or a collection. 是的,您需要使用parallelparallelStream方法请求并行性,具体取决于您是从流还是集合开始。

Regarding no guarantees, sure, there are never any guarantees in life. 对于没有保证,当然,生活中从来没有任何保证。 :-) After all, if you're running on a single-core system, nothing can run in parallel. :-)毕竟,如果你在单核系统上运行,没有什么可以并行运行。 Another scenario is, in an applet, the security manager might prohibit the application from using more than a single thread. 另一种情况是,在applet中,安全管理器可能会禁止应用程序使用多个线程。 In practice, in most environments, requesting a parallel stream will indeed split up the workload and run the tasks in parallel. 实际上,在大多数环境中,请求并行流确实会分散工作负载并并行运行任务。 By default, these tasks run in the common fork-join pool , which by default has as many threads as there are cores in the system. 默认情况下,这些任务在公共fork-join池中运行,默认情况下,该具有与系统中的核心一样多的线程。 But somebody might have set the number of threads to a different number, or even to 1, which is one reason the API itself cannot provide any guarantees. 但有人可能已将线程数设置为不同的数字,甚至设置为1,这是API本身无法提供任何保证的一个原因。

6: Ultimately, there is no guarantee that all the cores will be utilized as thread scheduling is NOT a JVM responsibility, rather dictated by OS. 6:最终,不能保证所有核心都将被利用,因为线程调度不是JVM的责任,而是由OS决定。 ... As various Java 8 blogs says just that "use this new parallelStream() and you will get parallel processing out of the box (for free and you as an application programmer are freed from having to worry about that)", my question in one sentence would have been is that really correct all the time? ...正如各种Java 8博客所说的那样“使用这个新的parallelStream(),你将获得开箱即用的并行处理(免费,你作为应用程序员免于担心这一点)”,我的问题一句话本来就是真的正确吗?

As above, no guarantees. 如上所述,没有保证。 There are many layers in the system where things can take a left turn. 系统中有许多层可以左转。 Even if your common FJ pool has as many threads as there are cores, there are no guarantees that each Java thread has its own OS thread. 即使您的公共FJ池具有与核心一样多的线程,也不能保证每个Java线程都有自己的OS线程。 (In the Hotspot JVM I think this is always true though. It depends on the JVM.) There could be other processes -- even other JVMs -- on the same system competing for cores, so your application might not get as many cores as you'd like. (在Hotspot JVM中,我认为这总是正确的。它取决于JVM。)在同一系统上可能有其他进程 - 甚至其他JVM - 竞争核心,因此您的应用程序可能没有像你想。 In that sense the JVM is at the mercy of the OS to schedule threads for it. 从这个意义上讲,JVM受操作系统的支配,可以为它安排线程。

I'm not sure where that blog entry came from, but the bit about parallel processing "for free" and the "you don't have to worry" sentiment is overblown. 我不确定博客条目的来源,但是关于并行处理“免费”和“你不必担心”的情绪被夸大了。 In fact, it's basically wrong. 事实上,这基本上是错误的。

It's true that it's possible to write a parallel stream much more conveniently than using earlier APIs. 确实,与使用早期的API相比,可以更方便地编写并行流。 But it's also possible to get it very, very wrong. 但也有可能让它变得非常非常错误。 If you put side effects into your stream pipeline, you'll have race conditions, and you might get a different wrong answer every time. 如果您将副作用放入流管道中,您将遇到竞争条件,并且每次都可能得到不同的错误答案。 Or, even if you take care to synchronize around side effects, you might create enough contention so that a parallel stream might run even slower than a sequential one. 或者,即使您注意围绕副作用进行同步,也可能会产生足够的争用,以使并行流的运行速度甚至比顺序流慢。

Even if you've managed to avoid these pitfalls, it's not the case that running a parallel stream on an N-core system will give you an N times speedup. 即使你已经设法避免这些陷阱,但并非如此,在N核系统上运行并行流将使你获得N次加速。 It just doesn't work that way. 它只是不起作用。 For small workloads the overhead of splitting and joining parallel tasks dominates, which can cause the computation to be slower than a sequential one. 对于小型工作负载,拆分和连接并行任务的开销占主导地位,这可能导致计算比顺序任务慢。 For larger workloads, the overhead is offset by the parallel speedup, but the overhead is still there. 对于较大的工作负载,开销由并行加速来抵消,但开销仍然存在。 The amount of speedup also depends on the nature of the workload, the splitting characteristics, lumpiness of the data, etc. Tuning a parallel application is something of a black art. 加速量还取决于工作负载的性质,分裂特性,数据的粗糙等。调整并行应用程序是一种黑色艺术。

For easily-parallelizable workloads, in my experience, it's pretty easy to max out a two-core system. 对于易于并行化的工作负载,根据我的经验,最大化双核系统非常容易。 A four-core system usually can get at least 3x speedup. 四核系统通常可以获得至少3倍的加速。 With more cores, it's not too difficult to get a 5x-6x speedup, but getting speedup beyond that requires actual work. 拥有更多内核,获得5x-6x加速并不困难,但加速超过这需要实际工作。

For not-so-easily parallelizable workloads, you might have to do a lot of thinking and restructuring of the application before you can even try running it in parallel. 对于不那么容易并行化的工作负载,您甚至可以在尝试并行运行之前对应用程序进行大量思考和重构。

I would not say that Java 8 gives you parallelism "for free" or "without worry" or anything like that. 我不会说Java 8为您提供“免费”或“无忧”或类似的并行性。 I would say that Java 8 gives you the opportunity to write parallel programs much more conveniently than before. 要说的是,Java的8给你编写并行程序比以前更方便的机会。 But you still have to work to get it correct, and you will probably still have to work to achieve the speedup that you want. 但你仍然需要努力使其正确,你可能仍然需要努力实现你想要的加速。

is that really correct all the time? 这一直是非常正确的吗?

It is correct all the time you want it to be. 它总是在你想要的时候是正确的。 The special allowance that a sequential stream is also OK was absolutely necessary to have this a useful feature at all: there will be many situations (perhaps testing, debugging etc.) where you will want a simple, sequential stream involved. 顺序流也是正常的特殊限制是绝对必要的,因为它有一个有用的功能:会有很多情况(可能是测试,调试等),你需要一个简单的顺序流。 Most concurrent issues begin their way to solution by trying to reproduce the issue in a non-concurrent setting. 大多数并发问题通过尝试在非并发设置中重现问题而开始解决问题。 Concurrent debugging being much harder, the first thing to make sure is that it is really needed. 并发调试要困难得多,首先要确保它确实是需要的。

You should never worry about CPU core utilization: this is old and stable technology, and they do get utilized in all my experience with Java. 你不应该担心CPU核心利用率:这是旧的,稳定的技术,并且他们得到应用在我与Java的所有经验。 If you are missing some percentage on your CPU utilization dashboard, you can be almost certain that the issues is solvable within Java , by streamlining locks and other thread coordination, rather than the fully correct Java program falling victim to the quirks of the runtime. 如果你缺少你的CPU利用率仪表板的一些百分比,你几乎可以肯定,这些问题是可以解决的在Java中 ,通过精简锁和其他线程协调,而不是完全正确的Java程序的牺牲品运行时的怪癖。

The other answers are basically correct. 其他答案基本上是正确的。 However, the parallel feature in Java8 is based on the Fork/Join framework. 但是,Java8中的并行功能基于Fork / Join框架。 Join() therein is unworkable so it was replaced by CountedCompleter. 其中的Join()不可行,因此被CountedCompleter取代。 That class is also flawed as I wrote in this article 正如我在本文中所写的那样, 这个类也有缺陷

The "depends" so often referred to in the other answers applies here as well. 其他答案中经常提到的“依赖”也适用于此。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM