一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情
ArrayList 主要方法
public boolean add(E e) //添加元素到末尾
public boolean isEmpty() //判断是否为空
public int size()
public E remove(int index) // 删除
FailFast 是什么
https://en.wikipedia.org/wiki/Fail-fast
wiki 百科的说法:
In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system's state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.
fail-fast 机制是java集合(Collection)中能被用来检测错误的一种错误机制。fail-fast 机制并不一定会发生。并发操作时,即当多个线程对同一个集合的内容进行操作, 可能会产生fail-fast事件。
看如下代码:
import java.util.ArrayList;
import java.util.List;
public class FastFailTest {
public static void main(String[] args) {
// 构建ArrayList
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
for (int i : list) {
System.out.println(i);
list.remove(1);
}
}
}
运行结果如下:
为啥会报错?
for each 循环其实是 java 将代码反编译后,发现 for 循环其实是个迭代器 iterator。
javac 编译后,javap -c 反编译可以看到 第 60 行 可以看到Iterator.hasNext
public class collection.FastFailTest {
public collection.FastFailTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: iconst_1
10: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
13: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
18: pop
19: aload_1
20: iconst_2
21: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
24: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
29: pop
30: aload_1
31: iconst_3
32: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
35: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
40: pop
41: aload_1
42: iconst_4
43: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
46: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
51: pop
52: aload_1
53: invokeinterface #6, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
58: astore_2
59: aload_2
60: invokeinterface #7, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
65: ifeq 99
68: aload_2
69: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
74: checkcast #9 // class java/lang/Integer
77: invokevirtual #10 // Method java/lang/Integer.intValue:()I
80: istore_3
81: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
84: iload_3
85: invokevirtual #12 // Method java/io/PrintStream.println:(I)V
88: aload_1
89: iconst_1
90: invokeinterface #13, 2 // InterfaceMethod java/util/List.remove:(I)Ljava/lang/Object;
95: pop
96: goto 59
99: return
}
通过代码可以看到
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at collection.FastFailTest.main(FastFailTest.java:15)
追踪到这一行
java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
这个代码,发现是iterator.next()方法中调用的
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
发现方法中对 modCount 和expectedModCount 进行了比较,不相等,会异常 ConcurrentModificationException。
expectedModCount 是 ArrayList 中对一个内部类的成员变量,是随着迭代器创建被初始化,通过迭代器对集合进行操作时,该值才会改变。
继续看代码,删除的核心逻辑如下:
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
发现删除的时候,只修改了 modCount, 并没有修改 expectedModCount的内容,这样就不相等了,主要原因是 remove 和 add 操作后造成 modCount, expectedModCount 不一致,就报错了。
正确的使用方式
通过迭代器删除。
public class FastFailTest {
public static void main(String[] args) {
// 构建ArrayList
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
// for (int i : list) {
// System.out.println(i);
// list.remove(1);
// }
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
if (iterator.next() == 1) {
iterator.remove();
}
}
}
}
原因是因为
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
只要不越界,就会有 expectedModCount = modCount;