zoukankan      html  css  js  c++  java
  • Java深入学习07:ConcurrentModificationException异常和CopyOnWriteArrayList

    Java深入学习07:ConcurrentModificationException异常和CopyOnWriteArrayList

    一、先看一个单线程Iterator遍历读写异常

    1-代码如下:创建一个ArrayList,并添加三个元素;开启1个线程,遍历该ArrayList,一边读取数据,一边删除数据

    public class CopyOnWriteArrayListTest {
        public static void main(String[] args) {
            CopyOnWriteArrayListThread t  = new CopyOnWriteArrayListThread();
            for(int i = 0; i< 1; i++){
                new  Thread(t).start();
            }
        }
    }
    
    class CopyOnWriteArrayListThread implements  Runnable{
        //方式1
        public static List<String> list = new ArrayList<String>();
    
        static{
            list.add("AA");
            list.add("BB");
            list.add("CC");
        }
    
    
        @Override
        public void run() {
            Iterator<String> iterator = list.iterator();
            while(iterator.hasNext()){
                String str = iterator.next();
                System.out.println(str);
    
                if(str.equals("CC")){
                    list.remove(str);
                }
            }
        }
    }

    2-结果如下:报ConcurrentModificationException异常

    java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
        at java.util.ArrayList$Itr.next(ArrayList.java:851)
        at juc.concurrentmap.CopyOnWriteArrayListThread.run(CopyOnWriteArrayListTest.java:42)
        at java.lang.Thread.run(Thread.java:748)

    3-分析ConcurrentModificationException异常的原因;

      在执行iterator.next()方法时,内部会先进行checkForComodification()判断,当 modCount != expectedModCount 时便抛出ConcurrentModificationException异常;

      其中modCount时ArrayList被修改的次数记录,expectedModCount是ArrayList修改次数的期望值,它的初始值为modCount;

       list.remove(str)操作内执行了modCount++;但是expectedModCount并额米有更新,所以报错;

    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];
    }
    
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

     4-单线程下的解决方案:改 list.remove(str)为iterator.remove();ArrayList中Iterator的remove方法,其中会更新expectedModCount 的值

        @Override
        public void run() {
            Iterator<String> iterator = list.iterator();
            while(iterator.hasNext()){
                String str = iterator.next();
                System.out.println(str);
    
                if(str.equals("CC")){
                    iterator.remove();
                }
            }
        }
    private class Itr implements Iterator<E> {
        //ArrayList中Iterator的remove方法
         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();
                }
            }    
    }       

    二、对于多线程,即使使用iterator.remove();还是会报ConcurrentModificationException;

    1-代码如下:创建一个ArrayList,并添加三个元素;开启10个线程,遍历该ArrayList,一边读取数据,一边删除数据

    public class CopyOnWriteArrayListTest {
        public static void main(String[] args) {
            CopyOnWriteArrayListThread t  = new CopyOnWriteArrayListThread();
            for(int i = 0; i< 10; i++){
                new  Thread(t).start();
            }
        }
    }
    
    class CopyOnWriteArrayListThread implements  Runnable{
        //方式1
        public static List<String> list = new ArrayList<String>();
    
        static{
            list.add("AA");
            list.add("BB");
            list.add("CC");
        }
    
        @Override
        public void run() {
            Iterator<String> iterator = list.iterator();
            while(iterator.hasNext()){
                String str = iterator.next();
                System.out.println(str);
                if(str.equals("CC")){
                    iterator.remove();
                }
            }
        }
    }

     异常日志

    java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
        at java.util.ArrayList$Itr.next(ArrayList.java:851)
        at juc.concurrentmap.CopyOnWriteArrayListThread.run(CopyOnWriteArrayListTest.java:46)
        at java.lang.Thread.run(Thread.java:748)

    2-分析问题:原因其实还是 modCount != expectedModCount 导致;可以这么理解modCount 参数属于list对象,即10个线程公用一个 modCount ;但 expectedModCount 属于list的 Iterator 对象,而list的 Iterator 对象在每一个新线程中,都会重新创建;则每个线程的expectedModCount 都是相互独立的,势必导致"公有变量" modCount 不等于 expectedModCount  的情况。

    3-解决方案:使用CopyOnWriteArrayList记录列表数据,并且删除数据时,使用list.remove(str);

    class CopyOnWriteArrayListThread implements  Runnable{
        //方式2
        public static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
    
        static{
            list.add("AA");
            list.add("BB");
            list.add("CC");
        }
    
        @Override
        public void run() {
            Iterator<String> iterator = list.iterator();
            while(iterator.hasNext()){
                String str = iterator.next();
                System.out.println(str);
                if(str.equals("CC")){
                    list.remove(str);
                }
            }
        }
    }

    三、有关CopyOnWriteArrayList

    1-CopyOnWriteArrayList是什么?

      根据官方的注解:CopyOnWriteArrayList是线程安全的ArrayList,其对应的add、remove等修改操作会同时创建一个新的副本

    2-CopyOnWriteArrayList如何做到线程安全

      add()和remove():CopyOnWriteArrayList类最大的特点就是,在对其实例进行修改操作(add/remove等)会新建一个数据并修改,修改完毕之后,再将原来的引用指向新的数组。这样,修改过程没有修改原来的数组。也就没有了ConcurrentModificationException错误。

        //CopyOnWriteArrayList中的add方法
        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();
            }
        }
         //CopyOnWriteArrayList中的remove方法,调用下面的 remove(o, snapshot, index)
        public boolean remove(Object o) {
            Object[] snapshot = getArray();
            int index = indexOf(o, snapshot, 0, snapshot.length);
            return (index < 0) ? false : remove(o, snapshot, index);
        }
         //真正的remove方法
        private boolean remove(Object o, Object[] snapshot, int index) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] current = getArray();
                int len = current.length;
                if (snapshot != current) findIndex: {
                    int prefix = Math.min(index, len);
                    for (int i = 0; i < prefix; i++) {
                        if (current[i] != snapshot[i] && eq(o, current[i])) {
                            index = i;
                            break findIndex;
                        }
                    }
                    if (index >= len)
                        return false;
                    if (current[index] == o)
                        break findIndex;
                    index = indexOf(o, current, index, len);
                    if (index < 0)
                        return false;
                }
                Object[] newElements = new Object[len - 1];
                System.arraycopy(current, 0, newElements, 0, index);
                System.arraycopy(current, index + 1,
                                 newElements, index,
                                 len - index - 1);
                setArray(newElements);
                return true;
            } finally {
                lock.unlock();
            }
        }

    get()

            //CopyOnWriteArrayList中的get方法
          public E get(int index) {
                final ReentrantLock lock = l.lock;
                lock.lock();
                try {
                    rangeCheck(index);
                    checkForComodification();
                    return l.get(index+offset);
                } finally {
                    lock.unlock();
                }
            }    
  • 相关阅读:
    Other.ini配置文件解读以及大众评委打分的最后得分两种模式选择及解析选项解释
    大作业练习:用Asp.net Mvc4做一个:学生考试成绩管理系统-简易版
    网络营销实施步骤及疑难问题汇编
    Web前端知识汇编收集B
    Web前端知识汇编收集A
    FlexItem 多行测试
    Last Work-随机出题加法游戏
    Android DisplayMetrics类获取屏幕大小
    Java简介
    Failed to resolve:junit:junit:4.12
  • 原文地址:https://www.cnblogs.com/wobuchifanqie/p/12510537.html
Copyright © 2011-2022 走看看