1 2 3 4

从 modCount 看 java集合 fail-fast 机制

一、背景

在常见的Java的非线程安全集合类中(如HashMap、ArrayList),经常可以在一些修改结构的操作(如Add)中看到实例变量 modCount++ ,来统计集合的修改次数。
从注释也可以看出,该字段是为 fail-fast(快速失败)机制服务。

二、简介

fail-fast 机制是能立刻报告任何可能导致失败的错误检测机制。

在java集合框架中表现为:当构建迭代器时,起初expectedModCount = modCount,当修改了该集合时,则该集合modCount++,随后迭代器在迭代下个元素时,会发现当前的modCount值与期望值expectedModCount不符,便会抛出ConcurrentModificationException异常,以避免数据不同步带来的麻烦。

注:

  1. 使用迭代器的remove()方法不会抛出异常,看源码可知在其中重新设置expectedModCount。
  2. 该机制不保证存在非同步并发修改时一定会抛出异常,只是尽最大努力去抛出该异常,因此最好不要依赖于该异常去写程序,而只是用于debug阶段。

官方注释:
Note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast operations throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

三、两种出现场景

3.1 单线程环境

在遍历集合的过程中,调用了集合修改方法。
例:

class Test{
    public static void main(String[] args){
        ArrayList<Integer> list = new ArrayList();
        list.add(1); list.add(2); list.add(3);

        Iterator itr = list.iterator();
        while(itr.hasNext()){
            System.out.println(itr.next());// 1 \n 2 \n 3
            itr.remove();  //后续不会抛出异常
        }
        System.out.println(list.toString());// 输出:[]
        list.add(1); list.add(2); list.add(3);

        itr = list.iterator();
        while(itr.hasNext()){
            Object i = itr.next();
            System.out.println(i);
            list.remove(i);  //后续next时会抛出异常
        }
    }
}

3.2 多线程环境

一个线程在遍历该集合时,另一个线程修改了该集合结构。

    ArrayList<Integer> list = new ArrayList();
    list.add(1); list.add(2); list.add(3);
	
    for(Integer i: list){
        new Thread(() -> {
            list.add(4);
        }).run();
        System.out.println(i);
    }

四、迭代器源码解析

以ArrayList对Iterator的实现为例:

    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;  // 期望的modCount值即为创建迭代器时的modCount值

        Itr() {}

        public boolean hasNext() { // hasNext 并不抛异常
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification(); //首先检查expectedModCount是否一致
            // …省略
        }
        
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;  //迭代器remove后不抛异常的原因,更新 expectedModCount
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        /** 检查 expectedModCount 与 当前 modCount是否一致,否则抛异常*/
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

参考:
https://www.geeksforgeeks.org/fail-fast-fail-safe-iterators-java/
https://blog.csdn.net/u014692324/article/details/78577130

posted @   SimpleIto  阅读(479)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
点击右上角即可分享
微信分享提示