You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:43:08