Java基础面试题05:简述快速失败(fail-fast)和安全失败(fail-safe)的区别 ?

server/2024/11/28 15:42:37/

在 Java 集合中,fail-fastfail-safe 是两种不同的遍历机制,用来定义在遍历集合时是否允许修改集合内容。它们的区别在于:

  • fail-fast:不允许在遍历过程中修改集合,一旦发现修改,立刻抛出异常。
  • fail-safe:允许在遍历过程中修改集合,因为它实际上是操作集合的“副本”。

fail-fast(快速失败)

fail-fast 的特点

  • 定义:在遍历集合时,如果检测到集合结构被修改(比如增删改),会立刻抛出 ConcurrentModificationException 异常。
  • 应用场景java.util 包下的集合(例如 ArrayListHashMap 等)通常使用 fail-fast 机制。
  • 发生原因:通过迭代器(Iterator)遍历时,如果在外部修改了集合内容,迭代器会检查集合的结构是否发生变化。一旦发现不一致,就触发异常。

单线程环境下 fail-fast 的例子

示例 1:ArrayList 的 fail-fast
java">public static void main(String[] args) {List<String> list = new ArrayList<>();for (int i = 0; i < 10; i++) {list.add(i + "");}Iterator<String> iterator = list.iterator();int i = 0;while (iterator.hasNext()) {if (i == 3) {list.remove(3);  // 修改集合,触发 fail-fast}System.out.println(iterator.next());i++;}
}

运行结果:程序抛出 ConcurrentModificationException,因为在迭代过程中直接修改了集合。

示例 2:HashMap 的 fail-fast
java">public static void main(String[] args) {Map<String, String> map = new HashMap<>();for (int i = 0; i < 10; i++) {map.put(i + "", i + "");}Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();int i = 0;while (it.hasNext()) {if (i == 3) {map.remove("3");  // 修改集合,触发 fail-fast}Map.Entry<String, String> entry = it.next();System.out.println("key= " + entry.getKey() + ", value= " + entry.getValue());i++;}
}

运行结果:同样会抛出 ConcurrentModificationException


多线程环境下 fail-fast 的例子

如果在多线程中,一个线程在遍历集合,另一个线程修改集合,也会触发 fail-fast。

java">public class FailFastTest {public static List<String> list = new ArrayList<>();private static class MyThread1 extends Thread {@Overridepublic void run() {Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {System.out.println(this.getName() + ": " + iterator.next());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}private static class MyThread2 extends Thread {int i = 0;@Overridepublic void run() {while (i < 10) {if (i == 2) {list.remove(i);  // 修改集合}System.out.println("thread2: " + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}i++;}}}public static void main(String[] args) {for (int i = 0; i < 10; i++) {list.add(i + "");}MyThread1 thread1 = new MyThread1();MyThread2 thread2 = new MyThread2();thread1.setName("thread1");thread2.setName("thread2");thread1.start();thread2.start();}
}

运行结果:线程 1 遍历时,线程 2 修改集合,抛出 ConcurrentModificationException


fail-fast 的工作原理

fail-fast 的核心在于一个叫 modCount 的字段。

  • modCount 是集合类中的一个变量,用来记录集合被修改的次数。
  • 每次使用迭代器时,会把 modCount 的当前值保存到 expectedModCount
  • 在遍历过程中,迭代器会不断检查 modCountexpectedModCount 是否一致。如果不一致,就抛出异常。
关键源码解析(以 ArrayList 为例)
java">private class Itr implements Iterator<E> {int cursor;  // 当前遍历位置int lastRet = -1;  // 上一次返回的元素索引int expectedModCount = modCount;  // 初始化时保存 modCountpublic boolean hasNext() {return cursor != size;}public E next() {checkForComodification();int i = cursor;if (i >= size) throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;cursor = i + 1;return (E) elementData[lastRet = i];}final void checkForComodification() {if (modCount != expectedModCount) {throw new ConcurrentModificationException();}}
}

只要 modCount 被修改,checkForComodification() 就会抛出 ConcurrentModificationException


如何避免 fail-fast?

  1. 使用迭代器的 remove() 方法:迭代器的 remove() 方法会更新 modCount,避免异常。

    java">public static void main(String[] args) {List<String> list = new ArrayList<>();for (int i = 0; i < 10; i++) {list.add(i + "");}Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {if (iterator.next().equals("3")) {iterator.remove();  // 安全移除}}
    }
    
  2. 使用并发集合:如 CopyOnWriteArrayListConcurrentHashMap,这些集合采用 fail-safe 机制。


fail-safe(安全失败)

fail-safe 的特点

  • 定义:fail-safe 遍历操作时,是基于集合的副本进行的。
  • 应用场景java.util.concurrent 包下的并发集合(如 CopyOnWriteArrayListConcurrentHashMap 等)。
  • 原理:遍历时对集合内容进行拷贝,因此对原集合的修改不会影响遍历过程,也不会触发异常。

fail-safe 的优缺点

  • 优点:避免了 ConcurrentModificationException
  • 缺点:遍历的内容是副本,无法反映集合的最新状态;同时,拷贝集合会消耗更多内存。
示例:CopyOnWriteArrayList
java">public static void main(String[] args) {List<String> list = new CopyOnWriteArrayList<>();for (int i = 0; i < 10; i++) {list.add(i + "");}for (String s : list) {if (s.equals("3")) {list.remove(s);  // 不会触发异常}}System.out.println(list);
}

总结

  • fail-fast 是基于集合本体操作,检测到修改会抛异常,适用于单线程操作。
  • fail-safe 是基于集合副本操作,允许修改,但无法反映最新状态,适用于并发环境。

如何选择?

  • 单线程环境:可以用 fail-fast,注意使用迭代器的 remove()
  • 多线程环境:优先选择 fail-safe,例如 ConcurrentHashMapCopyOnWriteArrayList

最后说一句(求关注,求赞,别白嫖我)

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的
7701页的BAT大佬写的刷题笔记,让我offer拿到手软

本文,已收录于,我的技术网站 cxykk.com:程序员编程资料站,有大厂完整面经,工作技术,架构师成长之路,等经验分享

求一键三连:点赞、分享、收藏


http://www.ppmy.cn/server/145660.html

相关文章

「实战应用」如何用图表控件LightningChart .NET实现散点图?(一)

LightningChart .NET完全由GPU加速&#xff0c;并且性能经过优化&#xff0c;可用于实时显示海量数据-超过10亿个数据点。 LightningChart包括广泛的2D&#xff0c;高级3D&#xff0c;Polar&#xff0c;Smith&#xff0c;3D饼/甜甜圈&#xff0c;地理地图和GIS图表以及适用于科…

react函数式组件中的路由传参方式

React Router 提供了多种方式来传递路由参数&#xff1a; URL 路径参数&#xff1a;通过动态路由和 useParams 获取。查询参数&#xff1a;通过 useLocation 获取 URL 查询字符串。路由状态传递&#xff1a;通过 state 属性在导航时传递数据&#xff0c;不在 URL 中显示&#…

【贪心算法第七弹——674.最长连续递增序列(easy)】

目录 1.题目解析 题目来源 测试用例 2.算法原理 3.实战代码 代码分析 1.题目解析 题目来源 674.最长递增子序列——力扣 测试用例 2.算法原理 贪心思路 3.实战代码 class Solution { public:int findLengthOfLCIS(vector<int>& nums) {int n nums.size();in…

基础入门-Web应用架构类别源码类别镜像容器建站模版编译封装前后端分离

知识点&#xff1a; 1、基础入门-Web应用-搭建架构上的技术要点 2、基础入门-Web应用-源码类别上的技术要点 一、演示案例-架构类别-模版&分离&集成&容器&镜像 1、套用模版型 csdn / cnblog / github / 建站系统等 安全测试思路上的不同&#xff1a; 一般…

c++的虚继承说明、案例、代码

虚继承的基本概念 在 C 中&#xff0c;虚继承主要用于解决多继承时可能出现的菱形继承问题。菱形继承是指一个类有两个&#xff08;或更多&#xff09;子类&#xff0c;而这两个子类又同时继承自一个共同的基类&#xff0c;当这些子类又被另一个类继承时&#xff0c;就形成了菱…

python读txt文件时出现UnicodeDecodeError错误的解决

1 现象 在编写文件读的代码&#xff1a; src_file_path "a:\\src.txt" with open(src_file_path) as file:data file.readline()出现如下错误&#xff1a; > UnicodeDecodeError: gbk codec cant decode byte 0xab in position 2: illegal multibyte sequenc…

【网络安全设备系列】12、态势感知

0x00 定义&#xff1a; 态势感知&#xff08;Situation Awareness&#xff0c;SA&#xff09;能够检测出超过20大类的云上安全风险&#xff0c;包括DDoS攻击、暴力破解、Web攻击、后门木马、僵尸主机、异常行为、漏洞攻击、命令与控制等。利用大数据分析技术&#xff0c;态势感…

线索二叉树

1.什么是线索二叉树&#xff1f; 线索二叉树是一种特殊的二叉树&#xff0c;它在传统二叉树的基础上&#xff0c;利用节点中原本为空的指针域存储指向节点前驱或后继的信息&#xff0c;从而在遍历时不需要递归或栈辅助&#xff0c;能够高效地找到前驱或后继节点。 前序遍历&am…