前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java - Java集合中的快速失败Fail Fast 机制

Java - Java集合中的快速失败Fail Fast 机制

作者头像
小小工匠
发布2021-11-03 15:09:43
发布2021-11-03 15:09:43
98600
代码可运行
举报
文章被收录于专栏:小工匠聊架构小工匠聊架构
运行总次数:0
代码可运行

什么是 fail-fast

fail-fast 机制是Java集合(Collection)中的一种错误机制。

在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的结构进行了修改(增加、删除),则会抛出Concurrent Modification Exception 【并发修改异常】。

举个例子:

在多线程环境下,线程1正在对集合进行遍历,此时线程2对集合进行修改(增加、删除、修改), 很容易抛出Concurrent Modification Exception 。

当然了,在单线程的情况下,遍历时对集合进行修改(增加、删除、修改)也会抛出Concurrent Modification Exception

此类的返回的迭代器iteratorlistIterator方法是快速失败的:如果列表在任何时间后,迭代器创建结构修饰,以任何方式除非通过迭代器自身 remove或 add方法,迭代器都将抛出 Concurrent Modification Exception

因此,面对并发修改,迭代器快速而干净地失败,而不是冒着在未来不确定的时间出现任意、非确定性行为的风险。


源码解读

Itr

在遍历的时候对集合修改会发生fail-fast,遍历集合------> 迭代器

代码语言:javascript
代码运行次数:0
运行
复制
/**
     * An optimized version of AbstractList.Itr
     */
    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;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

看到了吧, checkForComodification

代码语言:javascript
代码运行次数:0
运行
复制
 final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

modCount != expectedModCount的时候抛出了ConcurrentModificationException

而在next方法中上来就是调用checkForComodification,所以遍历集合才会可能抛出并发修改异常。

那接下来就研究 modCount 和 expectedModCount 什么时候会不相等就行了呗。

  • 在创建一个迭代器后,expectedModCount的初始值就是modCount了,
  • 对集合修改会改变modCount
  • expectedModCount只会在迭代器的remove方法中被修改为modCount

这都是

中的内容,除了modCount 。 modCount 是ArrayList的常量,默认值 为0


为什么对集合的结构进行修改会发生并发修改异常-源码分析

那我们说,在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的结构进行了修改(增加、删除),则会抛出Concurrent Modification Exception 【并发修改异常】。

修改方法之 remove

modCount++ , 后面modCount会和expectedModCount不相等,进而抛出并发修改异常。


修改方法之 add

ensureCapacityInternal方法里对modCount++操作, 改变了modCount的值,所以调用

那set方法会触发 fast fail吗?

答案是不会。

set没有对modCount++,所以对集合的某个元素进行修改并不会fail-fast


案例分享

【案例一】

代码语言:javascript
代码运行次数:0
运行
复制
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
    String tmp = iter.next();
    System.out.println(tmp);
    if (tmp.equals("1")) {
        list.remove("1");
    }
}
代码语言:javascript
代码运行次数:0
运行
复制
 1
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at com.artisan.fastfail.FastFailTest.main(FastFailTest.java:25)

调用了 list# remove方法


【案例二】

代码语言:javascript
代码运行次数:0
运行
复制
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
    String tmp = iter.next();
    System.out.println(tmp);
    if (tmp.equals("3")) {
        list.remove("3");
    }
}
代码语言:javascript
代码运行次数:0
运行
复制
1
2
3

调用了 list# remove方法 , 居然没有抛出并发修改异常????

remove倒数第二个元素,然而这时就没有抛出异常了 。 再分析分析吧

cursor是下一个要返回的变量的下标

lastRet是上一个返回过的变量的下标

hasNext方法告诉我们只有在下一个变量的下标不等于size的时候会告诉我们集合还有下一个元素。

但是在remove的时候,size- -了,那么删除“3”这个元素后,size变为3,而此时cursor也是3,那么再走到hasNext时,就发现cursor和size相等了,那么就会退出遍历,“4”压根就不会被遍历到。

所以没有抛出异常,因为remove后就退出了,还没来得及走到next方法呢~


【案例三】

代码语言:javascript
代码运行次数:0
运行
复制
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
    String tmp = iter.next();
    System.out.println(tmp);
    if (tmp.equals("4")) {
        list.remove("4");
    }
}
代码语言:javascript
代码运行次数:0
运行
复制
1
2
3
4
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at com.artisan.fastfail.FastFailTest.main(FastFailTest.java:25)

接上个案例

那 删除“4”,也就是最后一个元素,按理说删了最后一个元素不就退出了吗?走不到下一次的next方法呀?

其实是不对的,删完“4”并没有就直接退出 ! remove后size变成了3,但此时cursor是4,那么走到hasNext时,发现4!=3,就会再次进入循环,那么结果…走到了next方法,抛出了异常。。。


【案例四】

代码语言:javascript
代码运行次数:0
运行
复制
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
for (String i : list) {
    if ("1".equals(i)) {
        list.remove("1");
    }
}
代码语言:javascript
代码运行次数:0
运行
复制
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at com.artisan.fastfail.FastFailTest.main(FastFailTest.java:22)

用增强for循环遍历的,反编译class , 和用迭代器实质是一样的 。


【案例五】

代码语言:javascript
代码运行次数:0
运行
复制
List<String> list = Arrays.asList("1", "2", "3", "4");
for (String i : list) {
    if ("1".equals(i)) {
        list.remove("1");
    }
}
代码语言:javascript
代码运行次数:0
运行
复制
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.remove(AbstractList.java:161)
	at java.util.AbstractList$Itr.remove(AbstractList.java:374)
	at java.util.AbstractCollection.remove(AbstractCollection.java:293)
	at com.artisan.fastfail.FastFailTest.main(FastFailTest.java:21)

用了Array.asList()方法生成的集合,抛出的是UnsupportedOperationException,发现asList生成的ArrayList是个静态内部类,并非java.util.ArrayList, 并没有这些方法。

所以不能对asList生成的ArrayList进行增删改

Java开发规范01 - 集合篇_Arrays.asList 坑


【案例六】

代码语言:javascript
代码运行次数:0
运行
复制
    List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        Iterator<String> iter = list.iterator();
        while (iter.hasNext()) {
            String tmp = iter.next();
            System.out.println(tmp);
            if (tmp.equals("1")) {
                iter.remove();
            }
        }
代码语言:javascript
代码运行次数:0
运行
复制
1
2
3
4

【案例七】

代码语言:javascript
代码运行次数:0
运行
复制
```java

// Java code to illustrate

// Fail Fast Iterator in Java

import java.util.HashMap;

import java.util.Iterator;

import java.util.Map;

public class FailFastExample {

代码语言:txt
复制
public static void main(String[] args)
代码语言:txt
复制
{
代码语言:txt
复制
	Map<String, String> cityCode = new HashMap<String, String>();
代码语言:txt
复制
	cityCode.put("Delhi", "India");
代码语言:txt
复制
	cityCode.put("Moscow", "Russia");
代码语言:txt
复制
	cityCode.put("New York", "USA");
代码语言:txt
复制
	Iterator iterator = cityCode.keySet().iterator();
代码语言:txt
复制
	while (iterator.hasNext()) {
代码语言:txt
复制
		System.out.println(cityCode.get(iterator.next()));
代码语言:txt
复制
		// adding an element to Map
代码语言:txt
复制
		// exception will be thrown on next call
代码语言:txt
复制
		// of next() method.
代码语言:txt
复制
		cityCode.put("Istanbul", "Turkey");
代码语言:txt
复制
	}
代码语言:txt
复制
}

}

代码语言:txt
复制
```javascript

India

Exception in thread "main" java.util.ConcurrentModificationException

代码语言:txt
复制
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)
代码语言:txt
复制
at java.util.HashMap$KeyIterator.next(HashMap.java:1466)
代码语言:txt
复制
at FailFastExample.main(FailFastExample.java:18)
代码语言:txt
复制
​

```javascript

// Java code to demonstrate remove

// case in Fail-fast iterators

import java.util.ArrayList;

import java.util.Iterator;

public class FailFastExample {

代码语言:txt
复制
public static void main(String[] args)
代码语言:txt
复制
{
代码语言:txt
复制
	ArrayList<Integer> al = new ArrayList<>();
代码语言:txt
复制
	al.add(1);
代码语言:txt
复制
	al.add(2);
代码语言:txt
复制
	al.add(3);
代码语言:txt
复制
	al.add(4);
代码语言:txt
复制
	al.add(5);
代码语言:txt
复制
	Iterator<Integer> itr = al.iterator();
代码语言:txt
复制
	while (itr.hasNext()) {
代码语言:txt
复制
		if (itr.next() == 2) {
代码语言:txt
复制
			// will not throw Exception
代码语言:txt
复制
			itr.remove();
代码语言:txt
复制
		}
代码语言:txt
复制
	}
代码语言:txt
复制
	System.out.println(al);
代码语言:txt
复制
	itr = al.iterator();
代码语言:txt
复制
	while (itr.hasNext()) {
代码语言:txt
复制
		if (itr.next() == 3) {
代码语言:txt
复制
			// will throw Exception on
代码语言:txt
复制
			// next call of next() method
代码语言:txt
复制
			al.remove(3);
代码语言:txt
复制
		}
代码语言:txt
复制
	}
代码语言:txt
复制
}

}

代码语言:txt
复制
```javascript

1, 3, 4, 5

Exception in thread "main" java.util.ConcurrentModificationException

代码语言:txt
复制
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
代码语言:txt
复制
at java.util.ArrayList$Itr.next(ArrayList.java:851)
代码语言:txt
复制
at FailFastExample.main(FailFastExample.java:28)
代码语言:txt
复制



阿里巴巴Java开发手册中的规定


如何避免fail-fast抛异常

  1. 如果非要在遍历的时候修改集合,那么建议用迭代器的remove等方法,而不是用集合的remove等方法
  2. 并发的环境,那还要对Iterator对象加锁, 也可以直接使用Collections.synchronizedList
  3. CopyOnWriteArrayList(采用fail-safe)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/11/01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是 fail-fast
  • 源码解读
    • Itr
    • 为什么对集合的结构进行修改会发生并发修改异常-源码分析
      • 修改方法之 remove
      • 修改方法之 add
  • 案例分享
    • 【案例一】
    • 【案例二】
    • 【案例三】
    • 【案例四】
    • 【案例五】
    • 【案例六】
    • 【案例七】
  • 阿里巴巴Java开发手册中的规定
  • 如何避免fail-fast抛异常
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档