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

举报
bug菌 发表于 2024/10/30 21:33:27 2024/10/30
【摘要】   咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!!环境说明:Windows 10 +...

在这里插入图片描述

  咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~


🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!!

环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

@[toc]

📖 前言

在 Java 开发中,集合框架(Collection Framework)是我们处理数据时的重要工具。而 List 作为最常用的集合之一,提供了丰富的功能和灵活的使用方式。但在 List 的并发修改时,我们常会遇到一些意想不到的问题,比如 ConcurrentModificationException 异常。今天我们就来深入探讨一下 Java List 集合中的 Fail-FastFail-Safe 机制,希望帮助大家理解并掌握集合在并发修改中的表现。

📝 摘要

本篇文章围绕 Java 集合框架中的 List 的并发修改机制展开,主要探讨 Fail-Fast 与 Fail-Safe 两种机制的原理与实现。通过源码解读、案例分析等方式帮助大家了解这两种机制的区别、优缺点及使用场景,避免在并发环境中出现不必要的问题。

📜 简介

Java 的集合类在进行并发操作时,可能会引发一些问题,尤其是在对集合进行迭代的过程中修改集合本身时。这些操作往往会导致 ConcurrentModificationException 异常,这是由于 Fail-Fast 机制所致。为解决这一问题,Java 引入了 Fail-Safe 机制,它通过不同的方式来避免并发修改异常。接下来,我们将深入了解这两种机制的工作原理。

🌐 概述

Fail-FastFail-Safe 是 Java 集合框架中用于处理并发修改的两种不同机制:

  • Fail-Fast:在检测到集合被修改时立即抛出异常。
  • Fail-Safe:允许集合在被迭代的过程中进行修改,不会抛出异常。

🔍 核心源码解读

Fail-Fast 实现原理

Fail-Fast 机制的实现依赖于集合的结构修改计数器,即 modCount。当我们在迭代的过程中检测到 modCount 的值发生变化时,即视为集合发生了并发修改,抛出 ConcurrentModificationException 异常。

ArrayList 为例,modCount 通过如下代码实现:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    transient int modCount = 0;

    public E get(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        return elementData(index);
    }

    public E remove(int index) {
        modCount++;
        // 删除逻辑
    }
}

Fail-Safe 实现原理

Fail-Safe 机制通过创建集合的副本来进行迭代,因此不会影响原集合的内容。CopyOnWriteArrayList 是典型的 Fail-Safe 实现,它在写操作时复制一份新数组,迭代器遍历的是旧数组,避免了并发修改异常。

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private transient volatile Object[] array;

    public E get(int index) {
        return (E) getArray()[index];
    }

    public boolean add(E e) {
        synchronized (this) {
            Object[] newArray = Arrays.copyOf(array, array.length + 1);
            newArray[array.length] = e;
            array = newArray;
            return true;
        }
    }
}

💡 案例分析

Fail-Fast 案例

在 Fail-Fast 的场景中,对集合进行修改会导致 ConcurrentModificationException

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class FailFastExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        try {
            for (String item : list) {
                if (item.equals("B")) {
                    list.remove(item);  // 触发异常
                }
            }
        } catch (ConcurrentModificationException e) {
            System.out.println("Caught ConcurrentModificationException!");
        }
    }
}

在上述代码中,遍历过程中对集合进行了修改,导致触发 Fail-Fast 机制并抛出异常。

Fail-Safe 案例

使用 Fail-Safe 机制的 CopyOnWriteArrayList,则不会发生异常:

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

public class FailSafeExample {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        for (String item : list) {
            if (item.equals("B")) {
                list.remove(item);  // 不会抛出异常
            }
        }

        System.out.println(list);
    }
}

CopyOnWriteArrayList 中,迭代时不会影响集合的结构,因此不会出现异常。

代码解析:

在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

在这段代码中,使用了 CopyOnWriteArrayList,这是一个典型的 Fail-Safe 集合。当我们遍历并修改集合时,并不会抛出 ConcurrentModificationException 异常,这是由于 CopyOnWriteArrayList 在写操作时使用了副本机制来保证线程安全和 Fail-Safe 特性。

代码解析

import java.util.concurrent.CopyOnWriteArrayList;

public class FailSafeExample {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        for (String item : list) {
            if (item.equals("B")) {
                list.remove(item);  // 不会抛出异常
            }
        }

        System.out.println(list);
    }
}

具体解析

  • Fail-Safe 特性CopyOnWriteArrayList 的迭代器不直接引用原集合,而是对集合的快照进行遍历。因此,当 remove 方法在遍历中被调用时,它修改的是集合的副本,不会影响当前的迭代操作,从而避免了 ConcurrentModificationException 异常。

  • 线程安全CopyOnWriteArrayList 是线程安全的集合类。在写操作(如 addremove)时,它会复制一个新数组并修改该副本,然后将新的副本指向集合。因此,读取操作不会受到写操作的影响,这样可以确保线程安全性。

运行结果

当运行该代码时,list.remove("B") 不会抛出异常,最终的集合内容为 ["A", "C"]。输出结果如下:

[A, C]

🎬 应用场景演示

  • Fail-Fast 适合在 单线程不允许并发修改 的场景使用,比如对数据一致性要求较高的场景。
  • Fail-Safe 更适合在 多线程环境 中,允许对集合进行并发操作而不会影响迭代过程。

📝 优缺点分析

  • Fail-Fast

    • 优点:通过立即抛出异常,避免了并发修改导致的数据不一致性。
    • 缺点:不适合在多线程环境中使用,会频繁触发异常。
  • Fail-Safe

    • 优点:可以在多线程环境下使用,避免并发修改异常。
    • 缺点:复制集合导致内存开销较大,不适合大量数据的场景。

🧩 类代码方法介绍及演示

ArrayList 的 Fail-Fast 实现

public class FailFastList<E> extends ArrayList<E> {
    public boolean remove(Object o) {
        modCount++;  // 修改结构计数器
        return super.remove(o);
    }
}

CopyOnWriteArrayList 的 Fail-Safe 实现

public class FailSafeList<E> extends CopyOnWriteArrayList<E> {
    public boolean add(E e) {
        synchronized (this) {
            Object[] newArray = Arrays.copyOf(array, array.length + 1);
            newArray[array.length] = e;
            array = newArray;
            return true;
        }
    }
}

🧪 测试用例

Fail-Fast 测试

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("X");
    list.add("Y");

    for (String item : list) {
        list.remove("X");
    }
}

代码解析:

在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

在这段代码中,我们使用了 ArrayList 集合,并在遍历时对集合进行了修改。这会导致并发修改异常 (ConcurrentModificationException) 的发生,这是因为 ArrayListFail-Fast 的,在遍历时检测到结构被修改时会立刻抛出异常。

代码解析

import java.util.ArrayList;
import java.util.List;

public class FailFastTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("X");
        list.add("Y");

        for (String item : list) {
            list.remove("X");  // 触发 ConcurrentModificationException
        }
    }
}

解析

  • Fail-Fast 特性ArrayList 的迭代器(以及大多数集合的迭代器)在遍历时会监控集合的结构修改。如果在遍历过程中检测到集合的结构发生了变化(比如 remove 操作),就会抛出 ConcurrentModificationException 异常,以防止数据不一致性。
  • modCount 机制ArrayList 中维护了一个 modCount 变量,每次结构发生修改时都会更新 modCount,迭代器在遍历时会检查这个值是否发生变化,从而决定是否抛出异常。

运行结果

在运行该代码后,当 for 循环中的 list.remove("X") 被执行时,会引发 ConcurrentModificationException 异常,程序终止。

解决方案

  1. 使用显式迭代器:使用 Iterator 提供的 remove 方法来删除元素。

    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        String item = iterator.next();
        if ("X".equals(item)) {
            iterator.remove();  // 使用迭代器的 remove 方法,避免异常
        }
    }
    
  2. 使用 CopyOnWriteArrayList:如果需要在多线程或并发修改的情况下使用集合,可以考虑使用 CopyOnWriteArrayList,它是 Fail-Safe 的,不会抛出异常。

注意事项

  • Fail-Fast 集合适合单线程场景,如果需要并发修改,请考虑其他集合(如 CopyOnWriteArrayListConcurrentHashMap)。
  • 选择合适的集合和修改方法,避免出现并发修改异常,提高代码的健壮性。

Fail-Safe 测试

public static void main(String[] args) {
    CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
    list.add("X");
    list.add("Y");

    for (String item : list) {
        list.remove("X");  // 不会触发异常
    }
}

代码解析:

在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

在这段代码中,我们使用了 CopyOnWriteArrayList 集合,它是一种线程安全的、支持并发修改的列表。在遍历时对集合进行了修改,但并没有触发 ConcurrentModificationException 异常,这正是 CopyOnWriteArrayListFail-Safe特性。

代码解析

import java.util.concurrent.CopyOnWriteArrayList;

public class FailSafeTest {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("X");
        list.add("Y");

        for (String item : list) {
            list.remove("X");  // 不会触发异常
        }

        System.out.println("修改后的列表:" + list);
    }
}

解析

  • Fail-Safe 特性:在 CopyOnWriteArrayList 中,遍历操作不会直接访问原始集合,而是使用集合的快照。因此,即便在遍历过程中对集合进行修改,也不会触发异常。
  • 线程安全CopyOnWriteArrayList 在写操作(如 addremove)时会创建一个新的副本,旧的副本用于当前的遍历,新的副本包含修改后的数据。

运行结果

在运行该代码后,list.remove("X") 将会从集合中删除元素 "X",但不会引发任何异常,最终输出的列表会显示剩余的元素 ["Y"]

注意事项

  • CopyOnWriteArrayList 适合读多写少的场景,因为每次写操作都会生成新的副本,写操作开销较大。
  • 如果频繁进行写操作,CopyOnWriteArrayList 可能导致内存和性能消耗增加,因此需要根据场景合理选择集合类型。

🔍 测试结果预期与代码分析

在 Fail-Fast 场景中,测试会触发 ConcurrentModificationException,因为 ArrayList 检测到并发修改。而 Fail-Safe 场景中,CopyOnWriteArrayList 不会触发异常,因为迭代的是旧数据。

✨ 小结

Fail-Fast 和 Fail-Safe 是 Java 集合框架中非常重要的两种并发机制。在开发中,选择合适的机制可以避免并发异常,确保数据一致性。

🔚 总结与寄语

希望通过本文,你不仅掌握了 List 中的 Fail-Fast 和 Fail-Safe 机制,还能更好地选择和使用合适的集合类型。学习集合框架是一门细活,记住多观察、多实践!

  …

  好啦,这期的内容就基本接近尾声啦,若你想学习更多,可以参考这篇专栏总结《「滚雪球学Java」教程导航帖》,本专栏致力打造最硬核 Java 零基础系列学习内容,🚀打造全网精品硬核专栏,带你直线超车;欢迎大家订阅持续学习。

🌴附录源码

  如上涉及所有源码均已上传同步在「Gitee」,提供给同学们一对一参考学习,辅助你更迅速的掌握。

☀️建议/推荐你


  无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学Java」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门Java编程,就像滚雪球一样,越滚越大,指数级提升。

  最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。

  同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。

📣Who am I?

我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿哇。


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。