Java 之 fail-fast
发布日期:2021-05-08 17:19:34 浏览次数:21 分类:精选文章

本文共 3160 字,大约阅读时间需要 10 分钟。

fail-fast机制与ConcurrentModificationException

在软件开发中,fail-fast机制是一种强大的异常处理方式,能够及时发现潜在的系统故障。这种机制通过在异常发生时立即停止正常操作并报告错误,帮助开发者快速定位问题。然而,在Java的集合类应用中,fail-fast机制可能会引发ConcurrentModificationException(以下简称CME),这通常让程序员感到困惑,特别是在明明没有使用多线程的情况下。接下来,我们将深入探讨这个问题的本质及其解决方案。

fail-fast机制的定义

fail-fast机制的核心思想是在系统设计中优先考虑异常情况。一旦检测到潜在的错误或异常,系统会立即停止正常操作,并通过异常报告错误。这种机制通常用于防止程序在执行过程中因潜在错误而继续运行,从而避免数据不一致或其他严重后果。

一个简单的例子:

public int divide(int divisor, int dividend) {    if (dividend == 0) {        throw new RuntimeException("dividend can't be null");    }    return divisor / dividend;}

在上面的代码中,方法首先检查被除数是否为零,如果是,则立即抛出异常。这就是典型的fail-fast机制的应用。这种做法的好处是可以提前识别一些错误,并对这些错误进行针对性处理。

Java集合类中的fail-fast机制

在Java中,fail-fast机制尤其常见于集合类的使用。当多个线程试图对同一个集合进行结构修改时,集合会检测到这种并发修改,并通过抛出ConcurrentModificationException来警告程序员。这种机制默认是启用的。

CME的触发条件

CME通常在以下情况下发生:

  • 增强for循环中的修改操作:在foreach循环中对集合进行add或remove操作时,集合会检测到并发修改并抛出CME。
  • 直接使用集合方法进行修改:如调用remove(Object o)add(Object o)等方法进行操作时,如果在这个操作之前集合已经被其他线程修改过,同样会抛出CME。
  • CME的复现

    我们可以通过以下代码来复现CME:

    List
    userNames = new ArrayList<>();userNames.add("Hollis");userNames.add("hollis");userNames.add("HollisChuang");userNames.add("H");for (String userName : userNames) { if (userName.equals("Hollis")) { userNames.remove(userName); }}System.out.println(userNames);

    在上述代码中,foreach循环遍历集合并尝试删除"Hollis"元素。运行该代码会抛出CME,提示并发修改。

    CME的原理

    CME的触发源于集合的modCountexpectedModCount两个变量的不一致。modCount记录集合的修改次数,而expectedModCount是迭代器在创建时预期的修改次数。当迭代器检测到modCountexpectedModCount不一致时,就会抛出CME。

    modCount与expectedModCount的作用

    • modCount:每当对集合进行修改操作(如add、remove、clear等)时,modCount会被递增。
    • expectedModCount:当创建一个迭代器时,expectedModCount会被设置为当前的modCount。迭代器在每次调用next()时会检查modCount是否等于expectedModCount

    如果modCountexpectedModCount不一致,迭代器会抛出CME。

    CME的触发条件

    当在增强for循环中对集合进行修改操作时,集合会检测到并发修改,并通过抛出CME来警告程序员。这种机制的目的是防止在集合结构发生变化的情况下继续进行迭代,避免潜在的逻辑错误。

    如何解决CME问题

    为了避免CME带来的不便,可以使用一些特殊的集合类,这些集合类支持在foreach循环中进行元素的添加或删除操作,而不会抛出CME。这些集合类被称为fail-safe集合。

    CopyOnWriteArrayList

    CopyOnWriteArrayList是一个典型的fail-safe集合类。它的工作原理是基于写时复制的策略,确保在并发环境下可以安全地进行读写操作。

    CopyOnWriteArrayList的特点

  • 线程安全:支持多线程环境下的并发读写操作。
  • 读写分离:读操作不需要加锁,而写操作需要加锁进行保护。
  • 增强的兼容性:支持在foreach循环中进行add、remove等操作,而不会抛出CME。
  • CopyOnWriteArrayList的实现原理

    CopyOnWriteArrayList在写操作时会创建一个新的数组副本,然后在副本上进行操作。这样,在读操作时,直接访问副本中的数据,不会受到原集合的修改影响。

    CopyOnWriteArrayList的使用示例

    CopyOnWriteArrayList
    userNames = new CopyOnWriteArrayList<>();userNames.add("Hollis");userNames.add("hollis");userNames.add("HollisChuang");userNames.add("H");for (String userName : userNames) { if (userName.equals("Hollis")) { userNames.remove(userName); }}System.out.println(userNames);

    在上述代码中,使用CopyOnWriteArrayList进行增强for循环中的元素删除操作,运行时不会抛出CME。CopyOnWriteArrayList通过复制原集合,确保在读操作时不会受到并发修改的影响。

    Copy-On-Write机制

    Copy-On-Write(COW)是一种优化策略,通过延迟修改来减少对内存的占用。其核心思想是从共享的数据结构中延迟地创建新的副本,并在副本上进行修改,而不是直接修改原始数据结构。

    COW的优点

  • 减少内存占用:通过延迟修改,避免在读操作时占用过多内存。
  • 提高并发性能:支持在多线程环境下高效地进行读写操作。
  • 提供灵活性:允许在读操作时对集合进行修改,而不会影响读操作的结果。
  • COW的缺点

  • 增加了额外的开销:写操作需要额外的时间和资源来创建新的副本。
  • 不提供强一致性:读操作可能会看到旧的数据,而不是最新的修改结果。
  • 总结

    fail-fast机制是一种强大的异常处理机制,能够及时发现系统的潜在错误。然而,在Java的集合类中,fail-fast机制可能会在增强for循环中进行元素修改时抛出ConcurrentModificationException。为了避免这种情况,可以使用CopyOnWriteArrayList等fail-safe集合类,它们支持在foreach循环中进行元素的添加或删除操作,而不会抛出CME。

    上一篇:http/https/tcp/udp的区别
    下一篇:基础知识

    发表评论

    最新留言

    感谢大佬
    [***.8.128.20]2025年03月21日 19时15分04秒