并行流依赖Spliterator实现分片,因其提供trySplit()支持动态拆分、estimateSize()估算规模、characteristics()声明特征,而Iterator无法回答“能否拆、如何拆、剩多少”,故必须用Spliterator。

Java 8 的并行流能自动把集合分片交给多个线程处理,靠的不是魔法,而是 Spliterator —— 它才是并行拆分的真正执行者。普通 Iterator 没有拆分能力,一旦用它包装数据源(比如手动转成 StreamSupport.stream(iterator, false)),并行流就会退化为单线程执行。
为什么必须是 Spliterator,而不是 Iterator?
Iterator 只能“一个接一个”遍历,无法回答三个关键问题:
- 当前还剩多少元素?(
estimateSize()提供估算) - 能不能安全拆?比如是否有序、是否可变、是否支持并发读?(
characteristics()返回特征位) - 如果能拆,怎么切才均衡、无重叠、不遗漏?(
trySplit()实现具体分割逻辑)
而 Spliterator 把这三件事都封装好了。并行流在启动时,会调用集合的 spliterator() 方法拿到它,再反复调用 trySplit(),把原始数据不断“掰开”,直到子任务足够小或无法再拆,才丢进 ForkJoinPool 执行。
Spliterator 拆分不是简单二分,而是看“大小”和“特征”
拆分策略不是固定套路,而是动态决策:
立即学习“Java免费学习笔记(深入)”;
- 有
SIZED特征(如ArrayList、数组):知道确切剩余元素数,通常从中间切,左右两块大小接近,利于负载均衡 - 没有
SIZED(如LinkedList、自定义迭代器):estimateSize()可能返回 0 或Long.MAX_VALUE,trySplit()很可能直接返回null,导致整个流串行执行 - 带
CONCURRENT特征(如ConcurrentHashMap):底层已线程安全,Spliterator 可能选择不分割,让每个线程遍历全量——拆反而引入额外开销
也就是说,同一个 parallelStream() 调用,在 ArrayList 上可能起 4 个线程各处理 1/4 数据;在 LinkedList 上可能只起 1 个线程跑全程。
trySplit() 是核心动作:一次一拆,互斥覆盖
每次调用 trySplit(),最多生成一个新 Spliterator,原对象代表“剩下的部分”。两个结果必须满足:
- 合起来等于原始范围(无遗漏)
- 彼此之间没有交集(无重复)
- 不能修改原数据结构(除非本身支持并发)
注意:多次调用 trySplit() 不一定得到相同拆分结果。JDK 实现可能优先深挖(先拆到底)、也可能优先摊开(尽快生成多个子任务),取决于集合类型和 JVM 内部优化策略。
开发者能做什么?选对数据结构比调参数更有效
你不需要自己实现 Spliterator,但可以影响它的行为:
- 优先用
ArrayList、数组等带SIZED和SUBSIZED特征的集合,避免用LinkedList做并行计算 - 避免对
Stream.iterate()或Stream.generate()这类无限流或非 SIZED 流调用parallel(),它们的 Spliterator 无法准确估算大小,容易拆不准甚至死循环 - 若必须处理低效数据源,可考虑先
collect(Collectors.toList())转成 ArrayList 再并行,代价是内存换性能 - 调试时可用
spliterator().characteristics()查看实际特征值,验证是否符合预期
并行流的威力不在语法糖,而在 Spliterator 对数据可分割性的建模。理解它,才能避开“用了 parallel 却更慢”的坑。


















