zoukankan      html  css  js  c++  java
  • java基础解析系列(八)---fail-fast机制及CopyOnWriteArrayList的原理

    fail-fast机制及CopyOnWriteArrayList的原理

    目录

    先看一个例子

    class Te1 extends Thread
    {
        private List<Integer> list;
    
        public Te1(List<Integer> list)
        {
            this.list = list;
        }
    
        public void run()
        {
            Iterator<Integer> iterator = list.iterator();
            while(iterator.hasNext()){
                int i = iterator.next();
            }
        }
    }
    
     class Te2 extends Thread
    {
        private List<Integer> list;
    
        public Te2(List<Integer> list)
        {
            this.list = list;
        }
        public void run()
        {
            for (int i = 0; i < list.size(); i++)
            {
                list.remove(i);
            }
        }
    }
    public class Test {
        public static void main(String[] args) {
            ArrayList<Integer> list=new ArrayList();
            for (int i = 0; i <100 ; i++) {
                list.add(i);
            }
            Te1 t1=new Te1(list);
            Te2 t2=new Te2(list);
            t1.start();
            t2.start();
    
        }
    }
    
    • 一个线程迭代,一个线程进行删除,运行时抛出ConcurrentModificationException异常

    ConcurrentModificationException

    • 中文意思为并发修改异常
    736     public Iterator<E> iterator() {
    737         return new Itr();
    738     }
    743     private class Itr implements Iterator<E> {
    744         int cursor;       // index of next element to return
    745         int lastRet = -1; // index of last element returned; -1 if no such
    746         int expectedModCount = modCount;
    747 
    748         public boolean hasNext() {
    749             return cursor != size;
    750         }
    751 
    752         @SuppressWarnings("unchecked")
    753         public E next() {
    754             checkForComodification();
                    ...
    763         }
    764 
    765         public void remove() {
    766             if (lastRet < 0)
    767                 throw new IllegalStateException();
    768             checkForComodification();
                    ...
    778         }
    779 
    780         final void checkForComodification() {
    781             if (modCount != expectedModCount)
    782                 throw new ConcurrentModificationException();
    783         }
    784     }
    
    • ArrayList有一个内部类Itr,从源码可以看到这个类的next和remove方法里面都调用了一个chechForModification方法,而从这个方法(780行)的源码可以看到,他是通过判断modCount和expectedModCount是否相等来决定是否抛出并发修改异常
    • 同时在这个内部类可以看expectedModCount初始化为modCount(746行),后面并没有修改
    377     public boolean add(E e) {
    378         ensureCapacity(size + 1);  // Increments modCount!!
                ...
    381     }
    178     public void ensureCapacity(int minCapacity) {
    179         modCount++;
    180         ...
    189     }      
    439     public boolean remove(Object o) {
    440         if (o == null) {
    441             for (int index = 0; index < size; index++)
    442                 if (elementData[index] == null) {
    443                     fastRemove(index);
    444                     return true;
    445                 }
    446         } else {
    447             for (int index = 0; index < size; index++)
    448                 if (o.equals(elementData[index])) {
    449                     fastRemove(index);
    450                     return true;
    451                 }
    452         }
    453         return false;
    454     }
    460     private void fastRemove(int index) {
    461         modCount++;
                ...
    467     }
    
    • 从ArrayList的add和remove方法源码可以看到,这两个方法都会导致modCount的改变
    • 那么可以分析为什么之前的代码会抛出异常,线程A进行迭代,此时expectedModCount已经确定了,后面并没有进行修改,而此时线程B同时remove,从前面知道remove会导致modCount改变,此时两者不同导致抛出异常

    fail-fast

    A fail-fast system is nothing but immediately report any failure that is likely to lead to failure. When a problem occurs, a fail-fast system fails immediately.
    In Java, we can find this behavior with iterators. In case, you have called iterator on a collection object, and another thread tries to modify the collection object, then concurrent modification exception will be thrown. This is called fail-fast.
    
    
    
    • 中文译为快速失败,这是一种错误检测机制。
    • 对上文进行翻译,当在对一个集合进行迭代的时候,其他线程尝试去修改这个集合,并发修改异常会被抛出。这就叫做快速失败。

    CopyOnWriteArrayList

    • CopyOnWriteArrayList可以解决fail-fast的问题,将ArrayList替换成CopyWriteArrayList进行试验。
    public class Test {
        public static void main(String[] args) {
            CopyOnWriteArrayList<Integer> list=new CopyOnWriteArrayList();
            for (int i = 0; i <100 ; i++) {
                list.add(i);
            }
            Te1 t1=new Te1(list);
            Te2 t2=new Te2(list);
            t1.start();
            t2.start();
    
        }
    
    • 结果发现并没有抛出异常,下面从源码角度来分析
    • CopyOnWriteArrayList的remove方法
    469     public E remove(int index) {
    470         final ReentrantLock lock = this.lock;
    471         lock.lock();
    472         try {
    473             Object[] elements = getArray();
    474             int len = elements.length;
    475             E oldValue = get(elements, index);
    476             int numMoved = len - index - 1;
    477             if (numMoved == 0)
    478                 setArray(Arrays.copyOf(elements, len - 1));
    479             else {
    480                 Object[] newElements = new Object[len - 1];
    481                 System.arraycopy(elements, 0, newElements, 0, index);
    482                 System.arraycopy(elements, index + 1, newElements, index,
    483                                  numMoved);
    484                 setArray(newElements);
    485             }
    486             return oldValue;
    487         } finally {
    488             lock.unlock();
    489         }
    490     }
    99      final void setArray(Object[] a) {
    100         array = a;
    101     }
    
    • 473行获取当前的Object数组,480行创建一个新的Object数组,再将旧的数组复制到新的数组上,484行将array指向新的数组
    956     public Iterator<E> iterator() {
    957         return new COWIterator<E>(getArray(), 0);
    958     }
    991     private static class COWIterator<E> implements ListIterator<E> {
    992 
    993         private final Object[] snapshot;
    994 
    995         private int cursor;
    996 
    997         private COWIterator(Object[] elements, int initialCursor) {
    998             cursor = initialCursor;
    999             snapshot = elements;
    1000        }
    1001
    1002        public boolean hasNext() {
    1003            return cursor < snapshot.length;
    1004        }
    1005
    1010        @SuppressWarnings("unchecked")
    1011        public E next() {
    1012            if (! hasNext())
    1013                throw new NoSuchElementException();
    1014            return (E) snapshot[cursor++];
    1015        }
    1016
    
    • 999行将snapshot指向当前的array
    • 1011行执行next方法返回snapshot中元素,那么在遍历的过程,如果其他线程执行remove并将array指向了新创建的数组,这个snapshot并没有更新为新的数组,仍然指向的是remove之前的数组
    • 从CopyOnWriteArrayList的迭代器也可以发现没有fail-fast机制.

    CopyOnWriteArrayList分析

    • 修改代价大,可以从源码知道,remove还是add方法,都会进行一次数组的复制,这样消耗了空间(可能导致gc的频率提高)也消耗了时间
    • 读写分离,读写不一致,读的时候读的是旧的数组,写的时候写的是新的数组,所以读的时候不一定是最新的
    • 读的时候不需要进行加锁,因为写的时候是写在新的数组,读的数组是旧的数组,并不会改变
    • 因此,CopyOnWriteArrayList适合读多写少的场景

    我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)

    作者:jiajun 出处: http://www.cnblogs.com/-new/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

  • 相关阅读:
    探索数据
    Python基础15
    Jupyter Notebook 打开方法
    03-目录结构
    02-安装 CentOS7
    12-EndWithEstore
    11-Upload&Download
    10-Listener&Filter
    09-JDBC
    08-MVC&JavaBean
  • 原文地址:https://www.cnblogs.com/-new/p/7638302.html
Copyright © 2011-2022 走看看