Java并行处理填充ArrayList时元素缺失且存在空值的问题求助
问题分析与解决
你的核心问题出在非线程安全集合的并发写入:
- 代码里的
ret应该是普通ArrayList,而ArrayList的add()方法没有同步控制,并行流的多个线程同时调用add()会引发数据竞争:比如两个线程同时扩容、元素覆盖,最终导致结果列表长度不足,甚至出现空元素(部分写入操作被打断)。 - 另外你在并行流里直接修改原列表的元素
in.setImporteTotServ(),如果InformeFacturacionProcesado不是线程安全的类,多个线程同时修改同一个对象的属性也可能引发数据错乱。
修复方案
方案1:用并行流的collect替代forEach(推荐)
并行流的collect方法是线程安全的,它会为每个线程分配临时容器,最后统一合并,完全避免并发写入问题。修改代码如下:
List<InformeFacturacionAsociadoDto> ret = results.parallelStream() .map(in -> { Float reintegroConv = 0f; Float retribucion = 0f; ConvenioDto conv = new ConvenioDto(); /* 所有原有处理逻辑都保留,除了最后ret.add()的部分 */ String tipoPrescNombre = ""; String promoNombre = ""; String tipoPlantNombre = ""; String modeloPlantNombre = ""; String estadoPlantNombre = ""; String convenioNombre = ""; String tipoConvenioNombre = ""; String servicioNombre = ""; String nombreColaborador = ""; if (in.getTipoPrescriptorNombre() != null) { tipoPrescNombre = textosMap.get(Integer.parseInt(in.getTipoPrescriptorNombre())); } if (in.getPromocionNombre() != null) { promoNombre = textosMap.get(Integer.parseInt(in.getPromocionNombre())); } if (in.getTipoPlantillaNombre() != null) { tipoPlantNombre = textosMap.get(Integer.parseInt(in.getTipoPlantillaNombre())); } if (in.getModeloPlantillaNombre() != null) { modeloPlantNombre = textosMap.get(Integer.parseInt(in.getModeloPlantillaNombre())); } if (in.getServicioNombre() != null) { servicioNombre = textosMap.get(Integer.parseInt(in.getServicioNombre())); } if (in.getEstadoPlantillaId() != null) { estadoPlantNombre = EstadoPlantillaDto.getFromId(in.getEstadoPlantillaId()).getNombre(); } if (in.getImporteTotServ() == null) { in.setImporteTotServ(0f); } if (in.getConvenioId() != null) { conv.setTipo(in.getConvenioTipoId()); convenioNombre = in.getConvenioNombre(); tipoConvenioNombre = conv.getTipoNombre(); } if (in.getPodologoColaborador() != null && !in.getPodologoColaborador().equals("")) { nombreColaborador = in.getPodologoColaborador(); } if (in.getIva() != null && in.getIva() != 0) { in.setImporteTotServ(in.getBaseImponible() + (in.getBaseImponible() * in.getIva() / 100)); } else { in.setImporteTotServ(in.getBaseImponible()); } if (in.getConvenioId() != null && in.getServicioId() != null) { ConvenioServicio convServ = convServMap.get(in.getConvenioId() + "_" + in.getServicioId()); if (in.getEdadPac() != null && in.getConvenioId() != null && conv.isPartCon() && convServ != null) { if (in.getEdadPac() > Constants.PACIENTE_MENOR_EDAD) { reintegroConv = convServ.getPrecio_adulto_seguro(); } else { reintegroConv = convServ.getPrecio_infantil_seguro(); } } } if (in.getConsultaGenericaServicioNombre() != null && !in.getConsultaGenericaServicioNombre().equals("")) { reintegroConv = in.getPrecioSeguro() != null && in.getPrecioSeguro() > -1 ? in.getPrecioSeguro() : null; } float rConv = reintegroConv != null ? reintegroConv : 0; ClienteJcoPodologo comisionPod = null; if (in.getPodologoId() != null) { comisionPod = cliJroPodMap.get(in.getPodologoId() + "_" + in.getCliJurId()); } if (comisionPod != null && in.getServicioId() != null && in.getProductoNombre() == null) { float comision = 0; switch (in.getServicioTipoId()) { case Constants.TIPO_SERVICIO_PRINCIPAL: comision = comisionPod.getComisionBiomecanica(); break; case Constants.TIPO_SERVICIO_QUIROPODIA: case Constants.TIPO_SERVICIO_QUIROPODIA_CITA: comision = comisionPod.getComisionQuiropodia(); break; case Constants.TIPO_SERVICIO_YOUNEXT_BIKE: comision = comisionPod.getComisionYounext(); } retribucion = Precio.redondear((in.getBaseImponible() + rConv) * (comision / 100)); } String horaString = ""; try { SimpleDateFormat formatter = new SimpleDateFormat("HH:mm"); horaString = formatter.format(in.getFecha()); } catch (Exception e) { System.out.println(e.getMessage()); } // 直接返回构造好的对象,由collect自动收集 return new InformeFacturacionAsociadoDto( in.getFecha(), horaString, in.getCodigoFactura(), in.getCliJurNombre(), in.getCentroNombre(), in.getPodologoNombreFormal(), servicioNombre, in.getTipoRetoqueId(), in.getGarantia(), in.getEnCentro(), in.getProductoNombre(), in.getCodigoPlantilla(), tipoPlantNombre, modeloPlantNombre, estadoPlantNombre, in.getBaseImponible(), in.getIva(), in.getImporteTotServ(), reintegroConv, retribucion, in.getApellido1Pac() + " " + in.getApellido2Pac() + " ," + in.getNombrePac(), in.getProvinciaTxtPac(), in.getEdadPac(), tipoConvenioNombre, convenioNombre, promoNombre, tipoPrescNombre, in.getPrescriptorNombre(), formasPagoMap.get(in.getFormaPagoId()), 0/*paciente_id no sirve para este informe*/, nombreColaborador); }) .collect(Collectors.toList());
方案2:使用线程安全集合(不推荐,性能差)
如果坚持用forEach,把ret换成CopyOnWriteArrayList,它的add()方法是线程安全的,但每次添加都会复制整个数组,数据量大时性能极低:
List<InformeFacturacionAsociadoDto> ret = new CopyOnWriteArrayList<>(); results.parallelStream().forEach(in -> { // 原有处理逻辑不变,最后ret.add() });
额外注意点
你在并行流里修改了原列表的元素in.setImporteTotServ(),如果InformeFacturacionProcesado类的属性没有线程安全保障(比如没有用volatile或同步方法),多个线程同时修改同一个对象会导致数据错乱。如果原列表后续还要使用,建议不要在并行流里修改原对象,而是创建副本处理,或者确保原对象是线程安全的。
内容的提问来源于stack exchange,提问作者Bolskan
相关产品推荐
相关产品推荐

