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

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

相关推荐
勤奋的小王同学~1 分钟前
(UI自动化测试)web自动化测试
python
@逆风微笑代码狗1 分钟前
136.flask内置jinja2模版使用
后端·python·flask
好看资源平台6 分钟前
高级爬虫——数据清洗与处理
开发语言·爬虫·python
清山博客8 分钟前
Java将PDF保存为图片
java·开发语言·pdf
life102415 分钟前
pdf文档动态插入文字水印,45度角,旋转倾斜,位于文档中央,多行水印可插入中文
java·pdf·水印
归鸿铭16 分钟前
Html 转pdf
java·pdf·html
姜姜姜姜名字都有20 分钟前
青训10_1121_01_游戏排名第三大的分数
开发语言·python
new Vue()23 分钟前
ES6中Promise的使用场景
开发语言·前端·javascript
AI原吾25 分钟前
探索Python的Shell力量:Plumbum库揭秘
服务器·网络·python·plumbum
lix的小鱼29 分钟前
Scala之Array数组
java·开发语言·后端·python·scala