jdk8 并行流Parallel Stream 为啥很少有人用?他的缺点是什么?

面试题~

2 个回答

  • 古魔
    理解一切,没有对错,让花成花,让树成树。
    在 JDK 8 中引入了**并行流(Parallel Stream)**,它可以通过多线程的方式加快流操作的处理。然而,在实际开发中,并行流的使用并不如预期的那样广泛,原因主要与它的**缺点**和一些潜在的**问题**有关。 ### 并行流的缺点: 1. **线程管理不可控**: - 并行流使用的是**`ForkJoinPool.commonPool`**,这是一个全局的线程池。这个线程池的线程数量是固定的,通常等于系统的 CPU 核心数。如果程序中存在多个并行流操作,所有的并行流都会共享这个全局线程池,导致**线程竞争**和**资源争夺**。 - 由于开发者无法直接控制这个全局线程池的大小和配置,如果在应用中需要更精细的线程管理或者需要配置不同的线程池,**并行流的灵活性不够**。 2. **适用场景有限**: - 并行流并不适合所有场景。并行流对于**CPU 密集型任务**可以带来性能提升,但在**IO 密集型任务**或者**需要频繁同步的任务**中,并行流可能会**引发性能下降**,甚至比顺序流更慢。原因是:这些任务的并行化并不会带来太多并发优势,反而会因为线程切换、上下文切换、锁竞争等问题带来额外开销。 3. **任务划分不均导致的性能问题**: - 并行流依赖于底层的**`ForkJoinPool`** 框架来分割任务。如果任务不能很好地划分为相对均匀的小任务,那么并行流可能不会带来预期的性能提升,甚至可能造成**某些线程工作负担过重**,导致整体性能下降。比如,对于数据量小或者任务不平衡的数据集,并行处理可能反而不如单线程处理快。 4. **全局线程池的干扰**: - 由于并行流使用的是全局线程池,其他部分代码如果也依赖于 `ForkJoinPool.commonPool`,那么它们会相互干扰。如果某个部分代码耗尽了线程池的资源,可能会导致并行流任务得不到执行或者执行缓慢。 - 例如,某个应用同时有并行流和依赖 `ForkJoinPool` 的任务,并行流任务可能会因全局线程池资源被其他任务占用而表现不佳。 5. **难以调试**: - 并行流的内部是多线程并发执行的,调试并行流可能变得更加困难。尤其在并行任务出现问题时,比如线程安全问题、死锁等,定位问题变得更加复杂。 6. **线程上下文和局部变量问题**: - 在并行流中,每个操作可能会在不同的线程中执行。如果代码依赖于线程本地变量(如 `ThreadLocal`)或者某些线程上下文信息(如用户身份、事务信息等),并行流可能无法正确传播这些信息,从而导致逻辑错误。 7. **内存消耗增大**: - 并行流在处理大数据集时可能会创建大量的线程和中间对象,这会导致内存消耗增加,尤其是在**内存敏感**的场景中,使用并行流可能导致**垃圾回收压力增大**,甚至导致 `OutOfMemoryError`。 ### 并行流的适用场景: 虽然并行流有上述缺点,但它在某些特定场景下仍然有其优点和应用价值,尤其是对于**CPU 密集型任务**,或者在多核 CPU 上处理**大数据集**时,使用并行流可以显著提高性能。 适用的场景包括: 1. **数据量非常大**且数据处理可以并行执行的场景,例如统计、排序、筛选等纯计算操作。 2. **CPU 密集型任务**:这些任务不会因多线程并行处理产生太多线程间通信和锁竞争,能够充分利用多核 CPU。 3. **任务能够很好地拆分**,使得并行流可以将任务均匀分布到各个线程。 ### 如何更好地控制并行流: 1. **自定义线程池**: - 虽然并行流默认使用 `ForkJoinPool.commonPool`,但你可以通过 `ForkJoinPool` 来为并行流提供自定义的线程池。例如,可以通过以下方式来设置自定义线程池: ```java ForkJoinPool customThreadPool = new ForkJoinPool(4); // 自定义线程池 customThreadPool.submit(() -> list.parallelStream().forEach(...)).get(); ``` - 这种方式可以避免所有并行流共享一个全局线程池的问题,允许开发者根据场景需要配置线程池的大小。 2. **小心使用共享资源**: - 在使用并行流时,要避免在并行任务中使用共享资源(如可变的集合、全局状态变量等)。如果必须使用,可以考虑使用线程安全的数据结构,或者通过锁机制进行同步处理,尽量避免多线程并发引发的数据一致性问题。 ### 总结: **并行流的缺点**: - **线程管理不可控**,依赖全局线程池,无法灵活配置线程池。 - **性能不稳定**,在某些场景下可能比顺序流慢,尤其是任务划分不均或 IO 密集型任务。 - **调试困难**,多线程问题难以排查。 - **内存开销**,并行流可能导致内存使用增大,并带来垃圾回收压力。 - **不适合需要线程上下文信息的场景**,如 `ThreadLocal` 变量。 **使用并行流时的建议**: - 在**CPU 密集型任务**中并行流可能带来性能提升,但在 IO 密集型任务或小数据量处理时则不一定。 - 小心处理**共享资源**,避免数据竞争和线程安全问题。 - 在需要精细控制线程池的情况下,可以通过自定义线程池代替默认的 `ForkJoinPool.commonPool`。 由于这些缺点,在实际开发中,很多开发者会更倾向于手动管理多线程或者使用像 `ExecutorService` 这样更灵活的线程池,而不是使用并行流。
    发布于 2023-04-04 17:27 •IP属地 北京
    举报
    评论
    收藏

  • ketty
    不必太张扬,是花自然香。
    1.不要在高并发的接口中使用并行流,直接使用处理请求的线程执行就行,如果有需要,那就全局创建一个Fork-Join线程池自己切分任务来执行。 2.那些耗时很长的任务,请不要使用parallerStream。假设原本一个任务执行需要1分钟时间,有10个任务并行执行,如果你偷懒,只是使用parallerStream来将这10个任务并行执行,那你这个jvm进程中,其它同样使用parallerStream的地方也会因此被阻塞住,严重的将会导致整个服务瘫痪。 3.有i/o操作的一定不要使用并行流,有线程休眠的也一定不要使用并行流,原本就只有两个线程,还搞休眠,等着整个服务崩溃咯。 4.并行流还要自己保证线程安全问题。
    编辑于 2022-12-18 15:12 •IP属地 云南
    举报
    评论
    收藏