fail-fast机制(CopyOnWriteArrayList)

探讨Java集合中迭代器的快速失败机制,演示ConcurrentModificationException异常产生原因及解决方案,对比Vector与CopyOnWriteArrayList的线程安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、发现问题

这个机制在java集合中尤为常见。下面查看java结合根接口Collection

The default implementation creates a late-binding spliterator from the collections's {@code Iterator}.  
The spliterator inherits thefail-fast  properties of the collection's iterator.

也就说所有的集合中的迭代器都会有这个机制的检测。而且会抛出ConcurrentModificationException异常。

/**
 *This exception may be thrown by methods that have detected concurrent
 *modification of an object when such modification is not permissible.
 *For example, it is not generally permissible for one thread to modify a Collection
 *while another thread is iterating over it.
 */
 public class ConcurrentModificationException extends RuntimeException {
 }

下面演示这个异常:

package com.hfview.list;

import lombok.extern.slf4j.Slf4j;

import java.lang.invoke.VolatileCallSite;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * //TODO 写注释
 *
 * @author: zhw
 * @since: 2019/3/13 14:04
 */
@Slf4j
public class FailFase {

    static List<Integer> list = new ArrayList<>();

    public static void main(String[] args) throws Exception{



        ExecutorService executorService = Executors.newFixedThreadPool(10);

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                Iterator<Integer> it = list.iterator();
                log.debug("iterator method it.hasNext():"+it.hasNext());
                while(it.hasNext()){
                    log.debug("iterator method:");
                    System.out.println(it.next());
                }
            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10000;i++){
                    list.add((i+1));
                }
                log.debug(" add method end");
            }
        });

        executorService.shutdown();
    }
}

在这里插入图片描述
在迭代的国中添加元素就会发生异常。

当时放生这个问题的时候我第一发应是并发的读写并发执行,那么调用Vector就行了。
然后我就

static List<Integer> list = new Vector<>();

然而还是报错,打开源码我才发现,迭代器中有个检查方法(快速失败检查)

 final void checkForComodification() {
            if (modCount != expectedModCount)//expectedModCount是调用迭代器的时候当前的modCount 值
                throw new ConcurrentModificationException();
 }

原来在每次执行add的时候

public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

这样并行的迭代和添加肯定会造成modCount 的修改,会抛出异常。


二、如何解决

CopyOnWriteArrayList的出现,这个类使用和list完全一样,而且他是放到java.util.concurrent包下,那么可以肯定他是线程安全的,但是他不像Vector一样,他是绝对的线程安全。

分析add方法

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

很简单,获取数组,然后根据当前数据拷贝一个新数组,容量+1,然后赋值,最后把引用替换。

这也就说说迭代的对象数组和正在增加的操作的数组其实不是一个新的引用,这样就保证了不会出现问题

但是还是要慎用,因为缺点也很明显

  • 花费代价很大
  • 读和写中间过程会出现不一致的现象,但是保持最终一致性
`ConcurrentModificationException` 是 Java 中的一个运行时异常,通常发生在我们尝试修改集合(如 `ArrayList`, `HashMap` 等)的同时有另一个线程或迭代器正在访问该集合的情况。为了保证数据的一致性和防止潜在的数据损坏问题,Java 集合框架引入了 fail-fast 机制。 ### Fail-Fast 机制 Fail-fast 意味着“快速失败”。当检测到对集合进行并发修改时(例如,在遍历过程中直接添加、删除元素),就会抛出 `ConcurrentModificationException` 来立即报告错误而不是继续执行可能导致不确定结果的操作。这有助于开发者尽快发现并修复程序中的同步问题。 #### 典型场景 - **单线程环境**:即使是在单个线程内,如果在使用迭代器遍历时通过其他方式改变了列表的内容(除了 Iterator 自身提供的 remove/add 方法外),也会触发此异常。 - **多线程环境**:多个线程同时读取和更新相同的集合而未采取适当的锁机制保护资源安全的情况下会发生这种情况。 ### 解决方案 为了避免这个问题可以考虑以下几个方面: 1. 使用 synchronized 关键字手动加锁来控制对共享对象的访问; 2. 利用并发包下的容器类比如 ConcurrentHashMap 或 CopyOnWriteArrayList 这些专门设计用于处理高并发情况的安全结构; 3. 如果只是简单地想阻止外部改变 List 内容,则可以用 Collections.unmodifiableList() 包装原 list 得到只读版本; 4. 在一些特定场合下还可以选择采用更高效但稍微宽松一点的做法 - 比如允许有限度内的延迟刷新等策略,但这需要根据实际需求权衡利弊后再做决定。 总之,理解如何避免和妥善解决 `ConcurrentModificationException` 对编写稳定可靠的 Java 应用非常重要。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值