Java8 Stream结合CompletableFuture报End size小于fixed size异常咨询
这个IllegalStateException: End size X is less than fixed size Y是Java 8 Stream节点构建阶段的特有异常,核心触发逻辑是:Stream执行toArray操作时,会先根据源集合大小预估需要构建的固定容量节点,当最终写入节点的实际元素数量小于预估容量时,就会抛出该异常。
结合你的业务场景,具体触发原因分为两类:
1. 并行流JDK 8已知Bug
如果你的业务代码中给Stream加了parallel()转为并行流,就会触发Java 8的并行流实现缺陷:并行流多线程拆分处理元素时,FixedNodeBuilder的容量计数存在竞态问题,会导致实际写入元素数和预估容量不符,该问题在Java 9及之后的版本已经被官方修复。
2. 源集合被异步修改
你当前使用的源集合是ArrayList,属于线程不安全的实现,如果在Stream遍历执行的过程中,有其他线程对原List执行了新增/删除元素的操作,就会导致Stream初始预估的集合大小和实际能遍历到的元素数量不一致。你提到的“未修改关联集合”如果仅指当前线程未修改,不代表其他异步线程没有操作该集合,偶发不可复现的特性也符合多线程竞态修改的表现。
- 强制Stream串行执行:调用
stream().sequential()明确指定串行处理,避免并行流的实现问题 - 替换线程安全的源集合:将原ArrayList替换为
CopyOnWriteArrayList,或者在Stream遍历期间对原集合加锁,保证遍历过程中集合大小不会被修改 - 临时规避方案:如果必须在Java 8环境使用并行流,可以先将Stream的结果收集为List再转数组,即把
.toArray(CompletableFuture[]::new)替换为.collect(Collectors.toList()).toArray(new CompletableFuture[0]),绕开FixedNodeBuilder的容量校验逻辑 - 长期方案:升级JDK版本到11及以上,从根本上解决该实现缺陷
额外注意:你当前在
whenComplete回调中直接抛出BusinessException的写法存在隐患:该异常会直接抛到异步线程的调用栈,不会被主线程捕获,既不会触发allOf().join()抛出异常,还可能导致线程池的核心线程异常退出,建议改为通过CompletableFuture.completeExceptionally传递异常,或者在回调内自行捕获处理。
内容的提问来源于stack exchange,提问作者:林知行

