美文网首页
fail-fast和fail-safe事件

fail-fast和fail-safe事件

作者: 小明今晚加班 | 来源:发表于2019-05-23 15:25 被阅读0次
对比fail-fast和fail-safe

Iterator的安全失败是基于对底层集合做拷贝,因此不受源集合上修改的影响。java.util包下面的集合类在某些条件下回产生快速失败,而java.util.concurrent包下面的所有类都是安全失败的。快速失败的迭代器会抛出ConcurrentModidicationException异常,而安全失败的迭代器永远不会抛出这样的异常。

重点介绍fail-fast产生的原因以及解决办法

1.还原fail-fast实例

fail-fast产生条件:当多个线程对集合进行操作时,若其中一个线程通过Iterator去遍历集合时,该集合的内容被其它线程所改变,就会抛出ConcurrentModificationException异常。
异常代码如下:

package fail_fast;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.junit.Test;

/**
 * fail-fast异常的重现
 * 
 * @author cchzao
 *
 */
public class TestFailFast {

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

    public void printList() {
        Iterator<String> ite = list.iterator();
        while (ite.hasNext()) {
            String str = ite.next();
            System.out.println(str + "-");
        }
    }

    class MyThread1 extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                list.add(String.valueOf(i));
                printList();
            }
        }
    }

    class MyThread2 extends Thread {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for (int i = 5; i < 10; i++) {
                list.add(String.valueOf(i));
                printList();
            }
        }
    }

    @Test 
    public void test1(){
        new MyThread1().start();
        new MyThread2().start();
    }
}

输出结果如下:
0-
0-
1-
0-
1-
2-
0-
1-
2-
3-
0-
1-
2-
3-
0-
1-
2-
3-
4-
5-
0-
1-
2-
3-
4-
5-
6-
0-
1-
2-
3-
4-
5-
6-
7-
0-
1-
2-
3-
4-
5-
6-
7-
8-
0-
1-
2-
3-
4-
5-
6-
7-
8-
9-
Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)

在重现fail-fast实例中,有几次实验发现该异常并没有发生。那是为什么呢?因为fail-fast机制是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。因此在多线程环境中使用fail-fast机制的集合,建议使用“java.util.concurrent”包下的类去取代“java.util”包下的类。
比如可以使用java.util.concurrent包下的CopyOnWriteArrayList类替换ArrayList类。

2.从底层原理上分析为什么ArrayList会发生“java.util.ConcurrentModificationException”

我们关注Iterator是如何获取的:

1.我们的测试实例中这么写:Iterator<String> ite = list.iterator();
2.查看具体的iterator方法
/**
     * Returns an iterator over the elements in this list in proper sequence.
     *
     * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
     *
     * @return an iterator over the elements in this list in proper sequence
     */
    public Iterator<E> iterator() {
//发现,获取迭代器对象的时候,返回了一个Itr对象。
        return new Itr();
    }
3.进一步查看Itr对象的获取
/**
     * An optimized version of AbstractList.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;
        }

        @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
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            //找到原因了吧!!!
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

通过上述源码分析我们知道,ArrayList对象在执行next()、remove()方式时都会执行checkForComodification()方法,这个方法的作用就是查看modCount值和预期值是否一样?如果不一样就报ConcurrentModificationException异常。
这一点我在上上一篇介绍线程同步的时候也说过,当时发现很多线程不安全的方法中都有modCount变量,原来这个变量的作用在此体现出来了。
fail-safe的原因这里就不赘述了,直接翻看CopyOnWriteArrayList源码会发现,这个类底层采用的数组拷贝,自身实现的Iterator,更没有checkForComodification的方法,但是它的线程安全是通过域变量volatile来实现的。

相关文章

网友评论

      本文标题:fail-fast和fail-safe事件

      本文链接:https://www.haomeiwen.com/subject/vowvzqtx.html