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();
            }
        }    

 

posted on 2020-03-17 15:43  我不吃番茄  阅读(520)  评论(0编辑  收藏  举报