深入Java集合框架:解密List的Fail-Fast与Fail-Safe机制

开发 前端
对 List 的遍历和删除有个更清晰的认识!如果你正在设计一个并发系统,选择 fail-fast 还是 fail-safe 机制的集合类,将会显著影响系统的稳定性和性能。

图片图片

哈喽,大家好呀!我是小米,今天咱们来聊聊 Java 的 List 遍历和删除那些事儿。这方面其实有挺多坑,特别是并发场景下的小细节更是容易忽略。对了,今天我们要深入探讨两个很重要的机制——快速失败(fail-fast)和安全失败(fail-safe)。它们在遍历和删除元素时表现出的行为大有不同,尤其是在多线程环境下影响重大!

PART.01普通 for 循环删除 List 指定元素

最经典的操作,大家应该都用过普通的 for 循环遍历一个 List 来删除指定的元素。然而,简单粗暴的 for 循环却并不适用于所有情况,特别是涉及并发或动态修改结构时就容易出问题。

举个例子,我们想从一个List中删除所有偶数元素:

图片图片

为什么要 i-- 呢?

因为 List 是动态的数据结构,每次删除操作会让后续的元素往前移动一格,这会导致我们的索引不再准确,容易跳过元素。试想一下,如果删除了索引 1 的元素 2,那么原本在索引 2 的元素 3 就会移到索引 1 上去。如果没有减一操作,循环直接跳到下一个索引,3 就会被跳过。

小结

普通 for 循环适合简单的删除操作,但是在多线程和并发场景中,普通 for 循环删除元素会带来一些不安全性问题,这里就需要了解 fail-fast 机制啦。

PART.02使用迭代器遍历并删除元素

接下来,我们看看迭代器的操作。List 的 Iterator 是更常见的遍历方式,并且可以在遍历时安全删除元素——但前提是你得用对方法哦!

例如:

图片图片

在迭代器中,remove()方法是安全的。迭代器会维护集合的结构变化(modCount),所以在遍历期间不会抛出 ConcurrentModificationException 异常。

注意

在 Iterator 遍历中,直接对 List 调用 remove(i) 方法会触发 ConcurrentModificationException,因为迭代器无法跟踪通过 List 的直接删除操作。下面的代码就是个经典反例:

图片图片

PART.03foreach 循环删除元素

foreach 循环是一种简洁的写法,不过它也存在一些陷阱。即便在 Java 8 引入 forEachRemaining 方法后,foreach 依然无法实现边遍历边删除

图片图片


foreach 实质上是一个语法糖,底层依旧使用迭代器遍历,但不支持安全删除。为了避免异常,可以考虑先遍历收集要删除的元素,然后再进行批量删除。

PART.04快速失败(fail-fast)机制

这里要着重讲一下快速失败的机制了!fail-fast 在 Java 中主要用于检测集合在并发修改下的结构性变化。在遍历过程中,如果结构发生了变化,例如删除了元素,Java 会立刻抛出 ConcurrentModificationException 异常。

fail-fast 机制的背后是通过一个modCount 变量来实现的。每次集合结构发生变化时,modCount 的值会递增。迭代器在遍历时会检查 modCount 是否变化。如果变化了,说明集合被修改,就会立刻触发 ConcurrentModificationException

这种机制的好处是让程序及时发现问题,避免在集合状态不一致的情况下继续运行。但是也有局限性——它不适合并发环境。如果你必须在并发场景下安全操作 List,就需要了解 fail-safe 机制。

PART.05安全失败(fail-safe)机制

那么,什么是 安全失败(fail-safe)机制呢?

fail-safe 机制不同于 fail-fast,它不会直接访问原集合,而是会先创建一个集合的副本,迭代时操作副本内容,这样即便原集合被修改了也不会影响到当前遍历。不过,这种方式的缺点是,遍历期间集合的修改无法被同步感知java.util.concurrent 包下的许多集合类(如 CopyOnWriteArrayListConcurrentHashMap)都使用了 fail-safe 机制。

示例

图片图片

在这里,CopyOnWriteArrayList 采用了 fail-safe 机制,允许我们在遍历期间删除元素,不会抛出 ConcurrentModificationException但要注意:fail-safe 并发容器会在修改时消耗较多内存,因为它会创建副本。

使用场景

在高并发场景下,我们推荐使用 fail-safe 容器,比如 CopyOnWriteArrayList、ConcurrentHashMap 等。它们的 fail-safe 特性不仅可以避免异常抛出,而且能够确保在多线程环境下操作安全。但由于会创建副本,fail-safe 更适合读多写少的场景,否则内存和性能消耗会非常大。

END

我们来个小总结,看看这些遍历和删除操作的优缺点:

图片图片


希望大家读完这篇文章,对 List 的遍历和删除有个更清晰的认识!如果你正在设计一个并发系统,选择 fail-fast 还是 fail-safe 机制的集合类,将会显著影响系统的稳定性和性能。

责任编辑:武晓燕 来源: 软件求生
相关推荐

2023-09-14 16:10:36

机制fail-fast处理机制

2014-06-13 09:15:48

2021-04-06 16:25:56

分布式存储Fail-in-pla三副本架构

2015-09-11 09:40:35

Java集合框架

2012-03-12 15:36:29

Java框架

2014-09-24 09:27:02

2024-09-05 10:49:42

2010-09-14 09:30:04

Java多态

2020-07-15 20:32:45

fail2banFirewallD系统运维

2019-07-22 09:59:20

Java框架集合

2022-07-20 09:00:00

管理项目规模化敏捷框架科技

2012-04-26 10:52:52

Java数组集合

2011-07-11 11:02:12

JAVA集合框架

2021-03-11 07:27:22

Java 集合数据

2009-06-29 16:50:27

Java集合框架

2023-10-09 07:57:14

JavaJCF

2021-09-15 07:31:33

Android窗口管理

2024-09-30 09:13:14

协调通信机制

2024-11-08 17:15:49

2012-09-06 09:53:49

点赞
收藏

51CTO技术栈公众号