JAVA in fail-fast mechanism

In the JDK Collection, we often see something like this:

For example, ArrayList:

Note that the fail-fast behavior of an iterator can not be guaranteed because, in general, it is impossible for the presence of unsynchronized concurrent modification to make any hard guarantees. Fail-fast iterators will do our best to throw a ConcurrentModificationException. Therefore, in order to improve the accuracy of such iterators and write a program depended on this exception it is the wrong approach: the fail-fast behavior of iterators should be used only to detect bug.

HashMap中:

Note that the fail-fast behavior of an iterator can not be guaranteed, in general, the presence of asynchronous concurrent modification, it is impossible to make any hard guarantees. Fail-fast iterators do our best to throw a ConcurrentModificationException. Therefore, the practice of writing depended on this exception procedure is wrong, the correct approach is: fail-fast behavior of iterators should be used only to detect bugs.

Repeatedly referred to "fail fast" in these two words. So what is "fast failure" mechanism?

"Fast failure" that is fail-fast, which is a collection of Java error detection mechanism. When a plurality of threads set on the operation of changing the structure, there may have fail-fast mechanism. Remember that it is possible, but not certain. For example: Suppose there are two threads (Thread 1 and Thread 2), by a thread traversing Iterator element in set A, at some point the thread 2 modified the structure of the set A (the above structure is modified, rather than simply modify the contents of a collection of elements), so this time the program will throw an exception ConcurrentModificationException, resulting in fail-fast mechanism.

A, fail-fast exemplary

public class FailFastTest {
    private static List<Integer> list = new ArrayList<>();
    
    /**
     * @desc:线程one迭代list
     * @Project:test
     * @file:FailFastTest.java
     * @Authro:chenssy
     * @data:2014年7月26日
     */
    private static class threadOne extends Thread{
        public void run() {
            Iterator<Integer> iterator = list.iterator();
            while(iterator.hasNext()){
                int i = iterator.next();
                System.out.println("ThreadOne 遍历:" + i);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    /**
     * @desc:当i == 3时,修改list
     * @Project:test
     * @file:FailFastTest.java
     * @Authro:chenssy
     * @data:2014年7月26日
     */
    private static class threadTwo extends Thread{
        public void run(){
            int i = 0 ; 
            while(i < 6){
                System.out.println("ThreadTwo run:" + i);
                if(i == 3){
                    list.remove(i);
                }
                i++;
            }
        }
    }
    
    public static void main(String[] args) {
        for(int i = 0 ; i < 10;i++){
            list.add(i);
        }
        new threadOne().start();
        new threadTwo().start();
    }
}

operation result:

ThreadOne 遍历:0
ThreadTwo run:0
ThreadTwo run:1
ThreadTwo run:2
ThreadTwo run:3
ThreadTwo run:4
ThreadTwo run:5
Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at test.ArrayListTest$threadOne.run(ArrayListTest.java:23)

Two, fail-fast cause

Through the above examples and explanations, I initially know the reason fail-fast is that programs created for the collection when iterating the collection in a thread on its structure has been modified, then the iterator will throw ConcurrentModificationException abnormality information to generate fail-fast.

To learn more fail-fast mechanism, we must first understand ConcurrentModificationException abnormal. When the detected concurrent modification of an object, but is not permitted to be thrown such modifications. Also note that this exception does not always indicate the object has been concurrently modified by a different thread, if a single-threaded violates the rules, there are also likely to change throw an exception.

Indeed, the fail-fast behavior of an iterator can not be guaranteed, it can not guarantee that the error will occur, but quickly failed operation will do our best to throw ConcurrentModificationException exception, so therefore, in order to improve the accuracy of such operations depends on a written this exception program is the wrong approach, the correct approach is: ConcurrentModificationException should only be used to detect the bug. Now I will further analyze the reasons, for example ArrayList fail-fast produced.

We know from the front fail-fast operation is generated when the iterator. Now let's look at the source code for the ArrayList iterator:

private class Itr implements Iterator<E> {
        int cursor;
        int lastRet = -1;
        int expectedModCount = ArrayList.this.modCount;
 
        public boolean hasNext() {
            return (this.cursor != ArrayList.this.size);
        }
 
        public E next() {
            checkForComodification();
            /** 省略此处代码 */
        }
 
        public void remove() {
            if (this.lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            /** 省略此处代码 */
        }
 
        final void checkForComodification() {
            if (ArrayList.this.modCount == this.expectedModCount)
                return;
            throw new ConcurrentModificationException();
        }
    }

 We can see from the above source code, calling the iterator next (), when the remove () method is invoked checkForComodification () method, which is mainly detected modCount == expectedModCount? If you throw ConcurrentModificationException and other abnormalities, thereby generating mechanism fail-fast. So to figure out why have fail-fast mechanism we have to use understand why modCount! = ExpectedModCount, their value changes at what time.

        expectedModCount Itr is defined in: int expectedModCount = ArrayList.this.modCount; so he is not possible to modify the value, so that is becomes modCount. modCount is defined in AbstractList, the global variables:

protected transient int modCount = 0;

So when did he change because of what reason happen? See ArrayList source:

    public boolean add(E paramE) {
        ensureCapacityInternal(this.size + 1);
        /** 省略此处代码 */
    }
 
    private void ensureCapacityInternal(int paramInt) {
        if (this.elementData == EMPTY_ELEMENTDATA)
            paramInt = Math.max(10, paramInt);
        ensureExplicitCapacity(paramInt);
    }
    
    private void ensureExplicitCapacity(int paramInt) {
        this.modCount += 1;    //修改modCount
        /** 省略此处代码 */
    }
    
   public boolean remove(Object paramObject) {
        int i;
        if (paramObject == null)
            for (i = 0; i < this.size; ++i) {
                if (this.elementData[i] != null)
                    continue;
                fastRemove(i);
                return true;
            }
        else
            for (i = 0; i < this.size; ++i) {
                if (!(paramObject.equals(this.elementData[i])))
                    continue;
                fastRemove(i);
                return true;
            }
        return false;
    }
 
    private void fastRemove(int paramInt) {
        this.modCount += 1;   //修改modCount
        /** 省略此处代码 */
    }
 
    public void clear() {
        this.modCount += 1;    //修改modCount
        /** 省略此处代码 */
    }

We can see from the above source code, no matter ArrayList add, remove, clear as long as the method is directed to a method of changing the number of ArrayList elements will result in a change of modCount. So we are here to determine the initial value was due to a change expectedModCount modCount out of sync, leading to unequal between the two resulting in fail-fast mechanism. Know the root cause of fail-fast produced, we can have the following scenario:

There are two threads (thread A, thread B), which is responsible for traversing the thread A list, B modified thread list. A thread list traversal process at some point (in this case expectedModCount = modCount = N), the thread start, thread B while an additional element, the value of which is changed modCount (modCount + 1 = N + 1). A thread execution next proceeds to iterate the method, the method was found advertisement checkForComodification expectedModCount = N, and modCount = N + 1, ranging from two, then you throw ConcurrentModificationException exception, resulting in fail-fast mechanism.

So, until we have here fully understand the root cause of the fail-fast produced. Know the reason enough to find a solution.

Three, fail-fast solution

By the previous example, source code analysis, I think you have a basic understanding of the mechanisms fail-fast, propose a solution of the following reasons I produced. There are three solutions:

Scheme A: In the modifying operation when arraylist modCount the lock, while when the entire object locking arraylist iterator operation, thus ensuring that during the iterative perform, modCount can not be changed. It is not recommended, because of the deletions cause synchronization lock may block traversal operation.

Option Two: Try to use local variables, so fundamentally solve thread safety issues, while careful not to make changes on the list modcount operated under the same thread iterator process

Option Three: instead of using ArrayList CopyOnWriteArrayList. The program is recommended.

Why CopyOnWriteArrayList matter? ArrayList variant a thread-safe, in which all variable operation (add, set, etc.) of the underlying array is through a fresh copy achieved. The cost of such produce relatively large, but in both cases, it is very suitable for use. 1: synchronized traversal can not or do not want, but need to be excluded from the conflict in concurrent threads. 2: When the number of traverse operations greatly exceed the number of variable operation. CopyOnWriteArrayList encountered both cases use instead of ArrayList easy to do. So why CopyOnWriterArrayList ArrayList can replace it?

First, CopyOnWriterArrayList either from the data structure definitions are the same and ArrayList. It ArrayList, same as achieving the List interface, underlying the use of arrays to achieve. In the method also includes add, remove, clear, iterator like.

Second, CopyOnWriterArrayList would not produce ConcurrentModificationException abnormal, that is, it will not produce the iterator fail-fast mechanism. Take a look:

private static class COWIterator<E> implements ListIterator<E> {
        /** 省略此处代码 */
        public E next() {
            if (!(hasNext()))
                throw new NoSuchElementException();
            return this.snapshot[(this.cursor++)];
        }
 
        /** 省略此处代码 */
    }

CopyOnWriterArrayList methods simply do not use checkForComodification ArrayList method to determine whether equal expectedModCount with modCount like. Why would it do that, how can it do it? We add method, for example:

public boolean add(E paramE) {
        ReentrantLock localReentrantLock = this.lock;
        localReentrantLock.lock();
        try {
            Object[] arrayOfObject1 = getArray();
            int i = arrayOfObject1.length;
            Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);
            arrayOfObject2[i] = paramE;
            setArray(arrayOfObject2);
            int j = 1;
            return j;
        } finally {
            localReentrantLock.unlock();
        }
    }
 
    
    final void setArray(Object[] paramArrayOfObject) {
        this.array = paramArrayOfObject;
    }

CopyOnWriterArrayList and add add method is a method of ArrayList biggest difference is that the following three codes:

Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);
arrayOfObject2[i] = paramE;
setArray(arrayOfObject2);

This is the code that makes CopyOnWriterArrayList three ConcurrentModificationException not throw an exception. They exhibited charm lies copy the original array, then add the copy operation in the array, doing so will not affect COWIterator in the array.

Therefore, the core concept is represented CopyOnWriterArrayList: Any change in the array on the infrastructure operations (add, remove, clear, etc.), CopyOnWriterArrayList will copy existing data, and then copy the data on the changes, so it will not affect COWIterator in the data, after the completion of modifications to change the original data can be referenced. At the same time the cost of this is due to a large number of objects, while the copy of the array is quite lossy.

Standing on the Shoulders of Giants pick apples:

Original link: https: //blog.csdn.net/chenssy/article/details/38151189

Guess you like

Origin www.cnblogs.com/eternityz/p/12307120.html