Iterator_FailFast_FailSafe源码解析
一、源码演示
public class FailFastVsFailSafe {
// fail-fast 一旦发现遍历的同时其它人来修改,则立刻抛异常
// fail-safe 发现遍历的同时其它人来修改,应当能有应对策略,例如牺牲一致性来让整个遍历运行完成
private static void failFast() {
ArrayList<Student> list = new ArrayList<>();
list.add(new Student("A"));
list.add(new Student("B"));
list.add(new Student("C"));
list.add(new Student("D"));
for (Student student : list) {
System.out.println(student);
}
System.out.println(list);
}
private static void failSafe() {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
for (String li : list) {
System.out.println(li);
}
System.out.println(list);
}
static class Student{
private String name;
public Student(String name) {
this.name=name;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
failFast();
// failSafe();
}
}
1.failFast
我们在打印是给断点加情况,当遍历到C的时候断点停下来。然后在断点里加入E。
放行debug,显示并发修改异常。这是为什么呢?
一旦发现遍历的同时其它人来修改,就会抛出并发修改异常。这种行为就叫fail-fast.
2.failSafe
重复以上过程,我们对failSafe做遍历过程中的并发修改。如下图,并没有抛出异常。这种允许遍历的同时进行并发修改的行为就是failSafe。
在发现遍历的同时其它人来修改,应当能有应对策略,例如牺牲一致性来让整个遍历运行完成
那么ArrayList和CopyWriteArrayList如何分别保证failFast和failSafe的行为呢?接下来我们来解析一下源码。
二、源码解读
1.failFast
首先我们先给增强for循环打个断点。我们知道增强for循环的底层是迭代器,在第一次循环时会创建一个迭代器。我们force step into 进去看一下,可以看到会先创建一个迭代器对象。
点进去看一下,部分源码如下:
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;
// prevent creating a synthetic constructor
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
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int size = ArrayList.this.size;
int i = cursor;
if (i < size) {
final Object[] es = elementData;
if (i >= es.length)
throw new ConcurrentModificationException();
for (; i < size && modCount == expectedModCount; i++)
action.accept(elementAt(es, i));
// update once at end to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
源码解析如下:
- cursor`:记录下一个要返回的元素的索引。
lastRet
:记录上一个返回的元素的索引,初始值为 -1。expectedModCount
:记录迭代器创建时的modCount
值,用于检测并发修改。hasNext()
:判断是否还有下一个元素,通过比较cursor
和size
的值来确定。next()
:返回下一个元素,并更新cursor
和lastRet
。remove()
:移除上一个返回的元素,通过调用ArrayList
的remove()
方法实现,并更新相应的变量。forEachRemaining()
:接收一个Consumer
参数,对剩余的元素应用给定的操作。在遍历过程中,会检查并发修改。checkForComodification()
:用于检查是否有并发修改,通过比较modCount
和expectedModCount
的值来判断。
总体而言,Itr
类实现了迭代器的基本功能,包括遍历、移除和并发修改检测等。它与 ArrayList
类紧密结合,通过访问 ArrayList
的内部变量和方法来实现迭代操作。
我们来总结一下这个流程:
1.首先增强for循环在第一次循环时会创建一个迭代器对象。
2.接下来,他会调用构造器构造方法,然后初始化迭代器成员变量。
3.然后会调用hasNext()和next()方法去进行遍历。在next()方法中会调checkForComodification()方法用于检查是否有并发修改。
4.具体是通过比较 List的修改次数modCount
和迭代器的修改次数 expectedModCount
的值来判断,如果不等就抛出并发修改异常。
这就是ArrayList类failFast这一行为的整个过程了。
2.failSafe
接下来我们来看一下failSafe的源码,同样打断点进入。可以看到会新建一个COW迭代器,跟进去看一下。
源码如下:
static final class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array */
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;
COWIterator(Object[] es, int initialCursor) {
cursor = initialCursor;
snapshot = es;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
public boolean hasPrevious() {
return cursor > 0;
}
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
@SuppressWarnings("unchecked")
public E previous() {
if (! hasPrevious())
throw new NoSuchElementException();
return (E) snapshot[--cursor];
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
public void remove() {
throw new UnsupportedOperationException();
}
public void set(E e) {
throw new UnsupportedOperationException();
}
public void add(E e) {
throw new UnsupportedOperationException();
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int size = snapshot.length;
int i = cursor;
cursor = size;
for (; i < size; i++)
action.accept(elementAt(snapshot, i));
}
}
源码分析如下:
snapshot
:存储了一个CopyOnWriteArrayList
的快照,即迭代器创建时的数组状态。cursor
:表示下一个要返回的元素的索引。COWIterator
构造方法:接收一个数组es
和初始的cursor
值,用于初始化迭代器的状态。hasNext()
:判断是否还有下一个元素,通过比较cursor
和snapshot.length
的值来确定。hasPrevious()
:判断是否还有上一个元素,通过比较cursor
和 0 的值来确定。next()
:返回下一个元素,并将cursor
值加 1。previous()
:返回上一个元素,并将cursor
值减 1。nextIndex()
:返回下一个元素的索引。previousIndex()
:返回上一个元素的索引。remove()
、set()
、add()
:这些操作都抛出UnsupportedOperationException
异常,表示不支持对快照进行修改。forEachRemaining()
:接收一个Consumer
参数,对剩余的元素应用给定的操作。在遍历过程中,将cursor
设置为snapshot.length
,避免后续的迭代操作。
总体而言,COWIterator
类实现了 ListIterator
的基本功能,提供了迭代访问 CopyOnWriteArrayList
的元素。由于 CopyOnWriteArrayList
的迭代器操作的是一个快照,因此不支持对快照进行修改操作,并且在遍历过程中保持了对快照的一致性。
总结一下流程:
- 首先创建一个迭代器:在执行增强型
for
循环之前,会通过调用CopyOnWriteArrayList
的iterator()
方法创建一个迭代器。 - 获取迭代器的快照:迭代器内部会对
CopyOnWriteArrayList
进行快照,即复制一份当前的数组状态作为迭代器的数据源。 - 迭代器遍历:迭代器通过
hasNext()
和next()
方法依次遍历快照中的元素。这些方法是基于快照进行操作,因此对CopyOnWriteArrayList
的修改不会影响迭代器的遍历过程。 - 最后完成遍历:当迭代器遍历完所有元素后,循环结束。
- 并发修改检测:由于迭代器遍历的是快照而非原始数组,所以在遍历期间对
CopyOnWriteArrayList
的修改不会引发ConcurrentModificationException
异常。这是fail-safe
策略的特点。
注意: 在增强型 for
循环中,迭代器是只读的,因此不支持在循环中使用 remove()
、set()
、add()
等方法对集合进行修改操作。如果尝试在循环中进行修改操作,将会抛出 UnsupportedOperationException
异常。
总结:
1.failFast
-
ArrayList是fail-fast的典型代表,一旦发现遍历的同时其他人来修改,则立即抛异常。
-
实现原理:记录了循环开始时的次数,如果在循环的过程中修改次数被改,则会尽快失败,抛出异常,阻止循环继续。
2.failSafe
-
CopyOnWriteArrayList是fail-safe的典型代表,遍历的同时可以修改,原理是读写分离。
-
实现原理:读写分离。遍历时使用旧数组,在元素增加时创建一个新数组,长度是旧数组长度+1,然后将旧数组的元素copy到新数组中。添加是一个数组,遍历是另一个数组,互不干扰。遍历结束后回收旧数组,替换成新数组,于是在下次遍历的时候就遍历新的(添加后的)数组。
ArrayList是fail-fast的典型代表,一旦发现遍历的同时其他人来修改,则立即抛异常。
- 实现原理:记录了循环开始时的次数,如果在循环的过程中修改次数被改,则会尽快失败,抛出异常,阻止循环继续。