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

简介: 本文介绍了 Java 中 List 的遍历和删除操作,重点讨论了快速失败(fail-fast)和安全失败(fail-safe)机制。通过普通 for 循环、迭代器和 foreach 循环的对比,详细解释了各种方法的优缺点及适用场景,特别是在多线程环境下的表现。最后推荐了适合高并发场景的 fail-safe 容器,如 CopyOnWriteArrayList 和 ConcurrentHashMap。



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

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

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

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

为什么要 i-- 呢?

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

小结

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

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

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

例如:

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

注意

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

foreach 循环删除元素

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

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

快速失败(fail-fast)机制

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

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

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

安全失败(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 机制的集合类,将会显著影响系统的稳定性和性能。下次我们继续深入 Java 并发编程的更多实战小技巧,我们不见不散!

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号软件求生,获取更多技术干货!

相关文章
|
6天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
15天前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
基于开源框架Spring AI Alibaba快速构建Java应用
|
15天前
|
消息中间件 Java 数据库连接
Java 反射最全详解 ,框架设计必掌握!
本文详细解析Java反射机制,包括反射的概念、用途、实现原理及应用场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Java 反射最全详解 ,框架设计必掌握!
|
6天前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
14 2
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
10天前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
10天前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
10天前
|
Java 开发者
|
10天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
11 0
|
Java
Java中List排序的3种方法!(5)
Java中List排序的3种方法!(5)
347 0
Java中List排序的3种方法!(5)