【HashSet+并行流的分页踩坑分享】

Java分页结果一致性问题及解决方案

在开发分页功能时,我们经常会遇到一个看似简单但影响用户体验的问题:相同查询条件多次查询返回的结果不一致。这在使用HashSet和并行流时尤其常见。以下是一个完整的解决方案示例。

核心问题

/**
 * 人工置数配置表服务实现类
 * 
 * 注意:本类中大量使用LinkedHashSet而非HashSet,原因如下:
 * 1. HashSet无序性会导致分页结果不稳定,相同查询条件可能返回不同顺序的结果
 * 2. LinkedHashSet保持插入顺序,确保分页结果的一致性
 * 3. 在分页场景下,顺序一致性对用户体验至关重要
 * 
 * 注意:避免使用parallelStream(),因为:
 * 1. 并行流会导致结果不稳定,相同输入可能产生不同数量的结果
 * 2. 在分页场景下,结果的一致性比性能更重要
 * 3. 并行流与ArrayList等非线程安全集合的组合会导致并发问题
 */

问题代码

 /**
     * 构建总键集合
     */
    private Set<String> buildTotalKeys(List<EquipmentTempDTO> equipmentTempDTOS, ControlSettingQuerryDTO querryDto) {
        Set<String> totalKeys = new HashSet<>();
        Set<String> totalKeysTemp = equipmentTempDTOS.parallelStream()
                .flatMap(equipmentTempDTO -> {
                    // 解析模型模版
                    if (ObjectUtil.isNotEmpty(objectCase)) {
                        XmlModelUtil object = new XmlModelUtil();
                        XmlTypeDto result = new XmlTypeDto();
                        object.parseXMLToModel(objectCase, result);

                        List<String> keys = new ArrayList<>();

                        List<ModelXML> objectModelDigital = result.getObjectModelDigital();
                        if (CollUtil.isNotEmpty(objectModelDigital)
                                && (1 == querryDto.getPointType() || 0 == querryDto.getPointType())) {
                            objectModelDigital.parallelStream()
                                    .filter(modelXML -> Boolean.TRUE.equals(modelXML.getDisplay()))
                                    .map()
                                    .forEach(keys::add);
                        }

                        List<ModelXML> objectModelAnalog = result.getObjectModelAnalog();
                        if (CollUtil.isNotEmpty(objectModelAnalog)
                                && (2 == querryDto.getPointType() || 0 == querryDto.getPointType())) {
                            objectModelAnalog.parallelStream()
                                    .filter(modelXML -> Boolean.TRUE.equals(modelXML.getDisplay()))
                                    .map()
                                    .forEach(keys::add);
                        }

                        return keys.stream();
                    }
                    return Stream.empty();
                })
                .collect(Collectors.toSet());

        synchronized (totalKeys) {
            totalKeys.addAll(totalKeysTemp);
        }

        return totalKeys;
    }
### 解决方案实现

```java
/**
 * 构建总键集合
 * 使用LinkedHashSet保持元素的插入顺序,确保分页结果的一致性
 */
private Set<String> buildTotalKeys(List<EquipmentTempDTO> equipmentTempDTOS, ControlSettingQuerryDTO querryDto) {
    // 使用LinkedHashSet保持插入顺序,避免HashSet的无序性导致分页结果不稳定
    Set<String> totalKeys = new LinkedHashSet<>();

    // 使用顺序流避免并发问题,确保结果的一致性
    Set<String> totalKeysTemp = equipmentTempDTOS.stream()
            .flatMap(equipmentTempDTO -> {
               // 解析模型模版
                if (ObjectUtil.isNotEmpty(objectCase)) {
                    // 使用线程安全的集合来收集结果
                    List<String> keys = new ArrayList<>();
                    if (CollUtil.isNotEmpty(objectModelDigital)
                            && (1 == querryDto.getPointType() || 0 == querryDto.getPointType())) {
                        // 使用顺序流避免并发问题
                        objectModelDigital.stream()
                                .filter(modelXML -> Boolean.TRUE.equals(modelXML.getDisplay()))
                                .map()
                                .forEach(keys::add);
                    }

      
                    if (CollUtil.isNotEmpty(objectModelAnalog)
                            && (2 == querryDto.getPointType() || 0 == querryDto.getPointType())) {
                        // 使用顺序流避免并发问题
                        objectModelAnalog.stream()
                                .filter(modelXML -> Boolean.TRUE.equals(modelXML.getDisplay()))
                                .map()
                                .forEach(keys::add);
                    }

                    return keys.stream();
                }
                return Stream.empty();
            })
            // 按字符串排序以确保一致性
            .sorted()
            .collect(Collectors.toCollection(LinkedHashSet::new));

    totalKeys.addAll(totalKeysTemp);

    return totalKeys;
}

关键要点总结

  1. 使用LinkedHashSet代替HashSet

    • LinkedHashSet保持元素插入顺序
    • 确保相同查询条件返回一致的排序结果
    • 对分页功能至关重要
  2. 避免使用并行流

    • parallelStream()虽然能提升性能,但会破坏结果一致性
    • 在分页场景中,一致性比性能更重要
    • 并行处理与非线程安全集合结合易引发并发问题
  3. 添加排序操作

    • 使用.sorted()确保元素按固定顺序排列
    • 即使数据源顺序发生变化,最终结果仍然一致

实际效果

通过以上修改,可以确保:

  • 相同查询条件下分页结果始终保持一致
  • 用户刷新页面或重新查询时不会看到"跳跃"的数据
  • 提升用户体验和系统可靠性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值