当先锋百科网

首页 1 2 3 4 5 6 7

1 什么是fail-fast机制

在系统设计中,快速失效系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作,而不是试图继续可能存在缺陷的过程。这种设计通常会在操作中的多个点检查系统的状态,因此可以及早检测到任何故障。快速失败模块的职责是检测错误,然后让系统的下一个最高级别处理错误。实际上,fast-fail机制是一种理念:在做系统设计的时候先考虑异常情况,一旦发生异常,直接停止并上报。。

1.1 并发修改异常

我们首先以一个例子来导出并发修改异常,并分析产生该异常的原因,进而来了解fast-fail机制和JDK中常见迭代器的实现:

public class IteratorDemo {

    public static void main(String[] args) {

        Collection<String> list = new ArrayList<String>();

        list.add("Java");
        list.add("C++");
        list.add("Python");
        list.add("Shell");

        // 返回list集合的迭代器
        Iterator<String> iterator =  list.iterator();

        // boolean hasNext():如果迭代具有更多元素,则返回 true
        // E next():返回迭代中的下一个元素, 如果请求的元素不存在则报 NoSuchElementException
        while (iterator.hasNext()){
            String str = iterator.next();
            if(str.equals("Python")) {
                list.add("Go");  // ConcurrentModificationException: 并发修改异常

            }
            System.out.println(str);
        }
    }
}

程序运行结果:

Java
C++
Python
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at com.itheima.IteratorDemo.main(IteratorDemo.java:83)

Process finished with exit code 1

即:在调用ArrayList的内部类Itr的next方法时候报异常:ConcurrentModeificationException.

产生的原因:迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致。

了解该报错的具体原因,我们需要了解什么是ConcurrentModeificationException异常,以及ArrayList中迭代器的实现,以及jdk采用哪一种机制来保证迭代器访问集合时,集合结构不会发生变化。

1.2 ConcurrentModicationException

ConcurrentModicationException继承至RuntimeException。我们翻阅JDK源码可以看到对该异常的描述:
This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible.
For example, it is not generally permissible for one thread to modify a Collection while another thread is iterating over it. In general, the results of the iteration are undefined under these circumstances. Some Iterator implementations (including those of all the general purpose collection implementations provided by the JRE) may choose to throw this exception if this behavior is detected. Iterators that do this are known as fail-fast iterators, as they fail quickly and cleanly, rather that risking arbitrary, non-deterministic behavior at an undetermined time in the future.
Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.
Note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast operations throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

翻译成中文的大概意思是:
当对象的并发修改不允许时,检测到该修改的方法可能会引发此异常。

例如,通常不允许一个线程在另一个线程迭代集合时修改集合。一般来说,在这种情况下,迭代的结果是不确定的。如果检测到此行为,某些迭代器实现(包括JRE提供的所有通用集合实现)可能会选择抛出此异常。这样做的迭代器被称为fail fast迭代器,因为它们故障迅速且干净,而不是冒着在未来不确定时间出现任意、不确定行为的风险。

注意,这个异常并不总是表示一个对象被另一个线程同时修改。如果单个线程发出的方法调用序列违反了对象的约定,则对象可能会引发此异常。例如,如果线程在使用fail-fast迭代器迭代集合时直接修改集合,迭代器将抛出此异常。

注意,fail-fast行为不能得到保证,因为一般来说,在存在非同步并发修改的情况下,不可能做出任何硬保证。Fail fast操作会尽最大努力抛出ConcurrentModificationException。因此,编写一个依赖于此异常的正确性的程序是错误的:ConcurrentModificationException应该只用于检测bug。

2 分析ArrayList中的迭代器的实现

2.1 JDK源码分析ArrayList


public interface Iterable<T> { }
public interface Collection<E> extends Iterable<E> { }

public abstract class AbstractCollection<E> implements Collection<E> { }
public interface List<E> extends Collection<E> { }

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { }
public class ArrayList<E> extends AbstractList<E> implements List<E>, 
RandomAccess, Cloneable, java.io.Serializable { }

ArrayList继承体系图

2.2 modCount 变量的作用

JDK在Abstractlist抽象类中定义了modCount变量

那这个变量的是用来干什么的呢?JDK注释给出了解释
The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.
This field is used by the iterator and list iterator implementation returned by the iterator and listIterator methods. If the value of this field changes unexpectedly, the iterator (or list iterator) will throw a ConcurrentModificationException in response to the next, remove, previous, set or add operations. This provides fail-fast behavior, rather than non-deterministic behavior in the face of concurrent modification during iteration.
Use of this field by subclasses is optional. If a subclass wishes to provide fail-fast iterators (and list iterators), then it merely has to increment this field in its add(int, E) and remove(int) methods (and any other methods that it overrides that result in structural modifications to the list). A single call to add(int, E) or remove(int) must add no more than one to this field, or the iterators (and list iterators) will throw bogus ConcurrentModificationExceptions. If an implementation does not wish to provide fail-fast iterators, this field may be ignored.

翻译成中文的意思就是
modCount这个成员变量记录着集合的修改次数,也就每次add或者remove它的值都会加1,此变量用于集合迭代器迭代时候,保证集合的结构大小没有发生变化,否则会抛出ConcurrentModificationException异常。

2.3 ArrayList中的哪些方法会影响到modCount的值

ArrayList的 add、clear、remove等凡是改变List大小的方法在调用时,modCount都会自加。
即:modCount记录着修改集合的次数

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    public void clear() {
       modCount++;
       // clear to let GC do its work
       for (int i = 0; i < size; i++)
           elementData[i] = null;
       size = 0;
    }
    
    public E remove(int index) {
        rangeCheck(index);
        modCount++;
        E oldValue = elementData(index);
        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
        return oldValue;
    }

2.4 ArrayList的内部类Itr和Iterator()方法的实现

    public Iterator<E> iterator() {
        return new 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;

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

		/** 返回当前索引位置对应的元素后,索引+1 */
        @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();
            }
        }
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

分析:
1、在ArrayList对象调用iterator()方法返回迭代器对象的时候,迭代器对象内部变量expectedModCount记录着此时modCount的值。
2、每次调用迭代器对象的next()方法时,都会调用checkForComodification()方法去校验modCount 与 expectedModCount是否相等,如果不相等则抛出ConcurrentModificationException异常。即:当修改集合的次数和当时生成迭代器对象时保存的预期修改迭代器次数不一致时就会触发并发修改异常。

2.5 列表迭代器ListIterator

案例:

public class ListIteratorDemo {
    public static void main(String[] args) {

        List<String> list = new ArrayList<String>();

        list.add("Java");
        list.add("C++");
        list.add("Python");
        list.add("Shell");

        // 返回list集合的迭代器
        ListIterator<String> iterator =  list.listIterator();

        while (iterator.hasNext()){
            String str = iterator.next();
            if(str.equals("Python")) {
                iterator.add("Go");

            }
            System.out.println(str);
        }
    }
}

执行结果:

Java
C++
Python
Shell

Process finished with exit code 0

(1)通过List集合的listIterator()方法得到,它是List集合特有的迭代器。
(2)用于允许沿着任一方向遍历迭代的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置。

    public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }
    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }
        
        /**如果以逆向遍历列表,列表迭代器有多个元素,则返回 true。*/
        public boolean hasPrevious() {
            return cursor != 0;
        }
		
        public int nextIndex() {
            return cursor;
        }
        /**返回对 previous 的后续调用所返回元素的索引。*/
        public int previousIndex() {
            return cursor - 1;
        }

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

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();
            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

注意:列表迭代器允许对List进行操作add()、remove(), 操作的时候是调用迭代器的add()、remove()方法,而不是List的add()、remove()方法。