RMap的forEach不会触发并发修改异常?这是真的吗?

最近在对Redis数据进行遍历代码进行优化的时候,发现redis中的forEach不会触发并发修改异常,本文将为带你了解其中缘由

代码复现

原代码通过list收集待删除商品id,然后遍历删除,非常好的避免了并发修改异常,没有任何问题。

RMap<String, CartInfoDTO> cartMap = redissonClient.getMap(getCartKey(userId));
 ArrayList<String> deleteList = new ArrayList<>();
        // 遍历Map,获取被选中的商品Id
        cartMap.forEach((k, v) -> {
            if (v.getIsChecked().equals(1)) deleteList.add(k);
        });
        // 遍历删除商品Id
        deleteList.forEach(skuId -> cartMap.remove(String.valueOf(skuId)));

但下面这段代码,这里直接使用forEach循环,并且在循环过程当中,直接对RMap中的数据进行了修改,但是并没有触发并发修改异常,这就非常不符合我们的认知了。因为如果是Java中的普通Map对象,对此进行相同操作必然报出ConcurrentModificationException

RMap<String, CartInfoDTO> cartMap = redissonClient.getMap(getCartKey(userId));
 cartMap.forEach((k, v) -> {
            if (v.getIsChecked().equals(1)) cartMap.fastRemove(k);
        });

什么是并发修改异常?

在重点之前,先复习一下什么是并发修改异常,在Java中,并发修改异常(ConcurrentModificationException)是在用迭代器遍历集合对象时,如果集合内容被修改了就会抛出这个异常。

主要原因是迭代器在遍历时直接依赖集合的内部状态,一旦集合被修改,迭代器无法感知,导致其遍历结果不确定,所以会抛出这个异常提示用户集合正在被修改。
详见:https://www.jianshu.com/p/f3f6b12330c1

常见的并发修改异常场景

在foreach循环中直接删除集合元素

for (String str : list) {
  if (...) {
    list.remove(str); // 会抛出并发修改异常
  }
}

在遍历中直接修改集合内容

Iterator<String> it = list.iterator();
while (it.hasNext()) {
  String str = it.next();
  list.add("new"); // 会抛出并发修改异常
}

并发修改异常有什么用?

  • 安全性 - 防止遍历时出现不可预期的结果,避免程序出错。
  • 一致性 - 迭代器可以以一致的视图遍历集合,不会看到中间状态的数据。
  • 可见性 - 强制程序员注意到集合不能在遍历时被修改,考虑使用其他方法安全地删除。

报出该异常,对我们的编码规范带来一定好处:

  • 强制处理修改集合的安全性 - 不能随意在遍历时修改集合,会抛异常提醒。
  • 避免遍历时看到脏数据 - 一致的遍历视图,不会看到还未完全修改的中间状态。
  • 引导使用更好的方式 - 删除时使用迭代器自身的删除方法而不是直接删除等。

所以并发修改异常为遍历时对集合的修改添加了一层保护,维护代码的正确性和一致性。

Redis为什么能够避免并发修改异常?

当我们直接对 RMap 进行forEach遍历删除时,不会抛出并发修改异常,这是为什么呢?
主要原因如下:

  • RMap的遍历不是基于Iterator模式实现的

普通的Java集合在遍历时使用了Iterator模式来跟踪遍历状态。这会严格依赖集合内部的状态,一旦状态变化就会引发并发修改异常。

而RMap是基于Redis实现的分布式Map,它的遍历不依赖于集合内部的迭代器状态。

  • RMap的遍历是在一个一致性的快照上进行的

RMap的遍历会构建出一个遍历时的一致性视图,即使Map的数据变化,该快照也不会改变。所以遍历视图不会因为数据变化而引发异常。

  • 分布式Map的设计考虑了并发修改的问题

分布式Map天生需要面对多线程并发修改的情况,其遍历实现已经处理了并发修改的问题,不会由于数据变化而抛出异常。

总结

RMap 的forEach遍历不是依赖于 Iterator,而是内部实现了一个 Map 结构的快照,用于遍历处理。

所以直接对 RMap 清除数据时,是安全的,不会有并发修改异常。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值