for循环中list触发fast-fail或不触发的原理和方法

Iterable和Iterator


Iterator接口位于的位置是java.util.Iterator,它主要有两个抽象方法供子类实现。hasNext()用来判断还有没有数据可供访问,next()用来访问下一个数据。

集合Collection不是直接去实现Iterator接口,而是去实现Iterable接口。

用这个Iterable接口的iterator()方法返回当前集合迭代器。

集合Collection体系的集合都得按照使用iterator()的方式返回可供遍历的迭代器iterator

集合使用Iterable接口的iterator()方法返回迭代器Iterator有很多好处。

比如有两个线程都需要使用迭代器进行遍历集合的操作,如果通过直接实现Iterator接口来拿迭代器。首先两者拿的是同一个迭代器,并且两者不同步,一个都遍历结束了,另一个都还没开始就无法遍历集合了,但是他本来循环从头开始遍历集合的所有数据的。

如果使用Iterable接口的iterator()方法返回迭代器Iterator,那两者获得的就是不同迭代器了,就互不影响,自己可以按照自己的进度遍历集合就行。


for-each就是迭代器的语法糖,增强版的 for 循环(也叫 "for-each" 循环)在 Java 中是一个特性,它允许你遍历任何实现了 Iterable 接口的集合或者数组。

要使用增强型 for 循环(也称为 for-each 循环)遍历一个集合,集合的类需要实现 java.lang.Iterable 接口。Iterable 接口定义了一个方法 iterator(),它返回一个 Iterator 对象,用于遍历集合的元素。

java 复制代码
import java.util.Iterator;
import java.util.NoSuchElementException;

// Iterable位于java.lang包下,不用显式import
public class MyCollection<T> implements Iterable<T> {
    private T[] elements;
    private int size;

    @SuppressWarnings("unchecked")
    public MyCollection(int capacity) {
        elements = (T[]) new Object[capacity];
        size = 0;
    }

    public void add(T element) {
        if (size < elements.length) {
            elements[size++] = element;
        } else {
            throw new IllegalStateException("Collection is full");
        }
    }
	
	// 当一个类要实现 Iterable 接口,它必须提供一个 iterator() 方法,该方法返回一个 Iterator 对象。
    @Override
    public Iterator<T> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<T> {
        private int currentIndex = 0;

        @Override
        public boolean hasNext() {
            return currentIndex < size;
        }

        @Override
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return elements[currentIndex++];
        }
    }
}
java 复制代码
MyCollection<String> collection = new MyCollection<>(10);
        collection.add("a");
        collection.add("b");
        collection.add("c");

        // 使用增强型 for 循环
        for (String element : collection) {
            System.out.println(element);
        }

        // 和增强for循环等价的显式迭代器循环
        // 当一个类实现了 Iterable 接口,它必须提供一个 iterator() 方法,该方法返回一个 Iterator 对象。
        // 这个 Iterator 对象实现了 hasNext() 和 next() 方法,用于遍历集合中的元素。
        // 下面的 iterator 是通过集合类实现的 Iterable 接口的 iterator() 方法获得的
        Iterator<String> iterator = collection.iterator();
        while (iterator.hasNext()) {
            String element = iterator.next();
            System.out.println(element);

快速失败机制的工作原理

fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件

Java中的集合类(如ArrayList、LinkedList、HashMap等)使用快速失败机制来检测在迭代过程中对集合的结构性修改。

工作原理如下:

  1. modCount:每个集合类都有一个modCount字段,用于记录集合结构性修改的次数。结构性修改是指添加或删除元素,改变集合的大小。
  2. 迭代器的expectedModCount:当创建迭代器时,迭代器会保存当前集合的modCount值到一个名为expectedModCount的字段中。
  3. 一致性检查 :在每次调用迭代器的next()hasNext()方法时,迭代器首先会检查modCount是否与expectedModCount相同。如果不相同,说明集合在迭代过程中的结构被修改过,迭代器会抛出ConcurrentModificationException

总的来说:只要是涉及了改变ArrayList元素的个数的方法都会导致modCount的改变。当多线程环境下,由于expectedModCount与modCount的改变不同步,导致两者之间不等,从而产生fast-fail。

这里的抛出异常,停止执行就是fail-fast,就是当自己遇到无法处理的情况时的处理方式。

下面是ConcurrentModificationException异常的例子:

java 复制代码
List<String> list = new ArrayList<>();
list.add("a");
Iterator<String> iterator = list.iterator();  // expectedModCount = modCount = 0
list.add("b");  // modCount = 1
iterator.next(); // expectedModCount != modCount, 抛出ConcurrentModificationException异常

for-each循环对集合进行增删也可能抛出异常,因为for-each在反编译下可以发现就是迭代器的语法糖,所以涉及到对迭代器的使用。

java 复制代码
		List<String> collection = new ArrayList<>();
		collection.add("a");
		collection.add("b");
		// 使用增强型 for 循环
        for (String element : collection) {
            System.out.println(element);
        }

        // 和增强for循环等价的显式迭代器循环
        // 当一个类实现了 Iterable 接口,它必须提供一个 iterator() 方法,该方法返回一个 Iterator 对象。
        // 这个 Iterator 对象实现了 hasNext() 和 next() 方法,用于遍历集合中的元素。
        // 下面的 iterator 是通过集合类实现的 Iterable 接口的 iterator() 方法获得的
        Iterator<String> iterator = collection.iterator();
        while (iterator.hasNext()) {
            String element = iterator.next();
            if ("a".equals(element)) {
            	collection.remove(element);
            }
         }

想要在循环时进行增删操作,也就是进行这类对集合结构性有影响的操作,就要保证数据隔离性,下面是三种数据隔离性的处理方式,本质都是复制东西,只是复制的东西有所不同。

写入时复制(copy-on-write, 简称COW)。

GC 代表 "Garbage Collection"(垃圾回收)。COW读操作时并没有加锁,这是为了提高读操作的性能,但是有缺点,比如读数据的时候可能读不到最新的数据。例如,线程1往集合里面add数据才增加了一半,线程2这时候就去读数据,那读到的就还是老数据。

这样的话就只有增删才需要开辟一个新数组,其他情况都是使用原数组引用来读取原数组。

java 复制代码
// 可以像使用普通的 ArrayList 一样使用 CopyOnWriteArrayList(写入时复制),并且可以通过 List 接口来引用它
        List<String> cowList = new CopyOnWriteArrayList<>();
        cowList.add("a");
        cowList.add("b");
        cowList.add("c");

        Iterator<String> iterator = cowList.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        cowList.remove("b");
        Iterator<String> iterator2 = cowList.iterator();
        while (iterator2.hasNext()) {
            System.out.println(iterator2.next());
        }
相关推荐
Yan.love17 分钟前
开发场景中Java 集合的最佳选择
java·数据结构·链表
casual_clover17 分钟前
Android 之 List 简述
android·list
椰椰椰耶20 分钟前
【文档搜索引擎】搜索模块的完整实现
java·搜索引擎
大G哥21 分钟前
java提高正则处理效率
java·开发语言
指尖上跳动的旋律23 分钟前
shell脚本定义特殊字符导致执行mysql文件错误的问题
数据库·mysql
一勺菠萝丶34 分钟前
MongoDB 常用操作指南(Docker 环境下)
数据库·mongodb·docker
智慧老师1 小时前
Spring基础分析13-Spring Security框架
java·后端·spring
lxyzcm1 小时前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
m0_748244831 小时前
StarRocks 排查单副本表
大数据·数据库·python
V+zmm101341 小时前
基于微信小程序的乡村政务服务系统springboot+论文源码调试讲解
java·微信小程序·小程序·毕业设计·ssm