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

在 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 {
        @Override
        public 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;

        @Override
        public 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;  // 初始化时保存 modCount

    public 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:程序员编程资料站,有大厂完整面经,工作技术,架构师成长之路,等经验分享

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

相关推荐
老毛肚5 小时前
jeecg-boot-base-core 02 day
javascript·python
yaoxin5211236 小时前
434. Java 日期时间 API - Period 基于日期的时间段
java·开发语言·python
凡人叶枫6 小时前
Effective C++ 条款30:透彻了解 inlining 的里里外外
linux·开发语言·c++·嵌入式开发·effective c++
学逆向的6 小时前
C++纯虚函数
开发语言·c++·网络安全
何极光7 小时前
IDEA集成Maven
java·maven·intellij-idea
岁月宁静7 小时前
RAG 文档摄入全链路,从原理到生产落地
vue.js·人工智能·python
程序员二叉7 小时前
【JUC】ThreadLocal底层原理|内存泄漏|弱引用|跨线程传递方案
java·开发语言·面试·职场和发展·juc
程序员二叉7 小时前
【JUC】线程池全套深度详解|参数|流程|拒绝策略|调优|异常处理
java·开发语言·jvm·算法·面试·juc
JaydenAI7 小时前
[对比学习LangChain和MAF-07]如何引入人机交互的审批流程
python·ai·langchain·c#·agent·hitl·maf
老马识途2.07 小时前
在AI的帮助下理解spring的启动过程
java·前端·spring