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());
        }
相关推荐
yuanpan2 分钟前
MongoDB中的横向扩容数据分片
数据库·mongodb
草明4 分钟前
Mongodb 慢查询日志分析 - 1
数据库·python·mongodb
yuanpan5 分钟前
MongoDB的事务机制
数据库·mongodb
等一场春雨17 分钟前
Java设计模式 八 适配器模式 (Adapter Pattern)
java·设计模式·适配器模式
一弓虽39 分钟前
java基础学习——jdbc基础知识详细介绍
java·学习·jdbc·连接池
王磊鑫39 分钟前
Java入门笔记(1)
java·开发语言·笔记
SelectDB1 小时前
Apache Doris 2.1.8 版本正式发布
大数据·数据库·数据分析
硬件人某某某1 小时前
Java基于SSM框架的社区团购系统小程序设计与实现(附源码,文档,部署)
java·开发语言·社区团购小程序·团购小程序·java社区团购小程序
程序员徐师兄1 小时前
Java 基于 SpringBoot 的校园外卖点餐平台微信小程序(附源码,部署,文档)
java·spring boot·微信小程序·校园外卖点餐·外卖点餐小程序·校园外卖点餐小程序
chengpei1471 小时前
chrome游览器JSON Formatter插件无效问题排查,FastJsonHttpMessageConverter导致Content-Type返回不正确
java·前端·chrome·spring boot·json