Java Stream并行流parallel()配合skip()未输出元素1问题咨询
并行流skip场景下元素1未被peek打印的核心原因
skip(long n)是Stream API中的有状态中间操作,串行流与并行流对该操作的执行策略差异,是两段代码输出不一致的根本原因。
串行流的执行路径
串行流严格按照元素的遍历顺序逐元素执行整条操作流水线,单个元素走完从上游到下游的全部操作后,才会处理下一个元素。由于peek位于skip的上游位置,无论元素最终是否会被skip丢弃,都会先触发上游peek的逻辑:
- 元素1进入流水线:先执行
peek打印1,流转到skip(1)时被判定为需要跳过的首个元素,直接丢弃,不会进入后续map、forEach环节 - 元素2进入流水线:先执行
peek打印2,流转到skip(1)时跳过计数已耗尽,元素放行,经map转换为20后由forEach打印 - 元素3进入流水线:先执行
peek打印3,skip直接放行,经map转换为30后由forEach打印
该逻辑完全对应串行场景的输出结果。
并行流的执行路径
并行流会先将源数据拆分为多个分片,交由不同的工作线程并行处理。当流水线中存在有状态操作时,Stream框架会在有状态操作的节点做全局状态计算,为了减少不必要的计算开销,会直接在数据源分片阶段就把确定要丢弃的元素裁掉,根本不会送入后续操作流水线。
针对这段代码的skip(1)逻辑,框架会全局计算出需要丢弃遍历顺序下的第1个元素(也就是值为1的元素),直接在数据源层就把该元素排除在待处理分片外,它根本不会流到peek操作节点,自然不会触发打印。剩余的元素2、3会正常进入后续流水线,先触发peek打印原值,再经过map转换后由forEach打印,就得到了并行场景下的输出。
注意:这是Stream的设计特性而非Bug。Stream规范仅保证最终终止操作的输出符合操作规约,不承诺中间操作(尤其是
peek这类设计上仅用于调试的操作)会对所有源元素执行,在并行流+有状态操作的组合下,中间操作的执行范围、执行顺序、执行线程都没有确定性保证,不要在生产代码中依赖peek实现业务逻辑。
内容的提问来源于stack exchange,提问作者Jonatán Pallag
相关产品推荐
相关产品推荐

