【JavaSE】fail-fast and fail-safe source code analysis

1. Overview of fail-fast and fail-safe

快速失败(fail-fast), fail-fast is an error detection mechanism for Java collections.

  1. Scenario : When thread A is traversing a collection object using an iterator, thread B operates on the memory of the collection object (add, delete, modify), and at this time it will throwConcurrent Modification Exception
  2. Principle : For ArrayListexample, ArrayListan abstract class is inherited AbstractList, and there is a member variable in this abstraction protected transient int modCount = 0;, which records the number of times the collection has been modified. When a collection is traversed using an iterator, before the iterator is used hashNext()/next(), it will first check modCountwhether the variable is a expectedmodCountvalue, and if so, it will traverse; otherwise, an exception will be thrown.
  3. Note : The judgment condition for throwing an exception here modCountis whether the variable is expectedmodCounta value. If the collection changes, modCountthe value is just set to expectedmodCountthe value again. Therefore, you cannot program concurrent operations depending on whether this exception is thrown or not. This exception is only recommended for detecting concurrent modification bugs.
  4. Scenario: java.utilThe collection classes under the package are all fail-fast, and cannot be modified concurrently under multi-threading (modified during iteration), such as ArrayList classes.

安全失败(fail—safe), using a collection container that fails safely, it does not directly access the collection content when traversing, but first copies the original collection content, and then traverses on the copied collection.

  1. Principle : Since the copy of the original collection is traversed during iteration, the modification made to the original collection during the traversal process cannot be detected by the iterator, so it will not be triggered Concurrent Modification Exception.
  2. Disadvantages : The advantage of copying content is to avoid it Concurrent Modification Exception, but in the same way, the iterator cannot access the modified content, that is to say, the iterator traverses the copy of the collection obtained at the moment of traversal, and the original collection occurs during the traversal. The modified iterator is not known.
  3. Scenario : java.util.concurrentThe containers under the package are safe to fail, and can be used and modified concurrently under multiple threads, such as CopyOnWriteArrayListclasses.

2. Fail-fast source code analysis

Test code, use IDEA to debug

public static void main(String[] args) {
    
    
    ArrayList<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    for (Integer integer : list) {
    
    
        System.out.println(integer);
    }
}

The enhanced for loop here actually uses iterators to iterate. The iterator records the number of modifications to the current collection.

int expectedModCount = modCount;

Before the for loop, listfour elements are added to the collection, so the value listin modCountis 4.

listUse IDEA's debug tool to add an element - 5 - to the collection during program execution to simulate concurrency.

image-20230201234022290

image-20230201234042067

Successfully added a fifth element to the collection.

At this time, when the next enhanced loop is executed, that is, the method forof using the iteratorhasNext

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

Then execute the iterator nextmethod

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];
}

First, it will be called checkForComodification()to check whether the collection has been modified. If it is modified, the ones in the collection modwill not be expectedModCountequal.

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

modIf expectedModCountit is not equal, an exception will be thrown ConcurrentModificationExceptionand the program will be interrupted.

image-20230201234738430


3. Fail-safe source code analysis

Use CopyOnWriteArrayListfor testing.

public static void main(String[] args) {
    
    
    CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<Integer>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    for (Integer integer : list) {
    
    
        System.out.println(integer);
    }
}

Let's simply read CopyOnWriteArrayListthe source code first

//底层存放数据的数组
private transient volatile Object[] array;
public boolean add(E e) {
    
    
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    
    
        Object[] elements = getArray();
        int len = elements.length;
        //扩容
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
    
    
        lock.unlock();
    }
}

final Object[] getArray() {
    
    
    return array;
}

final void setArray(Object[] a) {
    
    
    array = a;
}

addmethod, its underlying implementation is locked ReentrantLock.

First get the length of the underlying array. Then expand. Then place the elements that need to be added to the collection at the end of the expanded array.

Then newElementsassign the new array to the underlying array array.

Next forbreak the point in the reinforcement loop.

image-20230202123227242

Create an iterator foris called when the enhanced loop is ready to begin .iteratorCOWIterator

public Iterator<E> iterator() {
    
    
    return new COWIterator<E>(getArray(), 0);
}

And the array that stores the data at the bottom layer is used arrayas a parameter.

//由后续调用next返回的元素的索引。
private int cursor;
//快照,存储的是迭代器进行迭代的时候,集合的数据
private final Object[] snapshot;
private COWIterator(Object[] elements, int initialCursor) {
    
    
    cursor = initialCursor;
    snapshot = elements;
}

and then call hasNextthe method

public boolean hasNext() {
    
    
    return cursor < snapshot.length;
}

Then, call nextthe method to output the array of snapshots in sequence.

public E next() {
    
    
    if (! hasNext())
        throw new NoSuchElementException();
    return (E) snapshot[cursor++];
}

If at this time, thread B adds an element to the collection. But the iterator snapshotdoes not change.

Because it can be fixed snapshotat the time of initialization. Even if things change COWIteratornext . Also remains the same.arraysnapshot

Therefore, the data output by the iterator is the data before the moment the iterator is created. During the iteration process, no matter what the original data of the collection becomes, the old data is output.


4. Summary

  1. ArrayListIt is fail-fasta classic representative that cannot be modified while traversing, otherwise an exception will be thrown.
  2. CopyOnWriteArrayListIt is fail-safea classic representative, which can be modified while traversing. The principle is to separate reading and writing.

Guess you like

Origin blog.csdn.net/weixin_51146329/article/details/128848313