1.背景(增强for循环中即使没有并发修改异常,但是也会抛出这个错误)

package com.ydlclass.collection;

import java.util.*;

public class ConcurrentModityTest {
    public static void main(String[] args) {
        test1();
    }
    public static void test1(){
        List<String> names = new ArrayList<>();
        Collections.addAll(names,"ssc","ssd","lili");
        for (String name : names) {//这种方式会导致并发修改的问题,从而抛出并发修改异常(即时没有并发也会抛出异常)
            if ("lili".equals(name)){
                names.remove(name);
            }
        }

    }
}

2.多线程并发修改的情况:

package com.ydlclass.collection;

import java.util.*;

public class ConcurrentModityTest {
    public static void main(String[] args) {
        test1();
    }
    public static void test1(){
        final List<Integer> nums = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            nums.add(new Random().nextInt(1000000000));
        }

        for (int i = 0; i < 20; i++) {//多线程并发修改;
            new Thread(()->{
                Iterator<Integer> iterator = nums.iterator();
                while (iterator.hasNext()){
                    Integer next = iterator.next();
                    if (next > 1000000){
                        iterator.remove();
                    }

                }
            }).start();
        }
    }
}

3.出现问题的原因在于:

3.1由于遍历是通过size()和游标来实现的,假设两个线程,其中一个已经获得了size,但是遍历期间被另个线程删除了元素,导致实际的size小于原先获取的,就会产生数据越界异常。从而抛出并发修改异常;

3.2CheckForModifyCount()会比较modCount与expectedModCount,前者是父类AbstractList的成员变量,后者是内部类迭代器的成员变量,所属的对象不同;方法中会比较两个值是否相同,modCount是每个线程都能获得的,expectedModCount是每个线程迭代器的期望修改次数,多线程修改共享变量,不能保证多线程原子序,有序性,可见性。就会产生问题;(迭代器首先让expectedModCount等于modCount,在remove方法中结束的时候在此执行前面的赋值操作,)

alt

4.分析原因:

 private class Itr implements Iterator<E> {
        /**
         * Index of element to be returned by subsequent call to next.
         */
        int cursor = 0;

        /**
         * Index of element returned by most recent call to next or
         * previous.  Reset to -1 if this element is deleted by a call
         * to remove.
         */
        int lastRet = -1;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;//迭代器中成员变量

        public boolean hasNext() {
            return cursor != size();
        }

        public E next() {
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {//next()方法或者是hasNext()方法都会先调用这个方法
        //判断期望的的修改次数是否等于实际的修改次数(实际的修改次数是父类的成员变量)
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
     

Note:多线程的情况下,假设线程A的迭代器中的成员变量exceptedModCount = 1 ,迭代器a遍历下次之前有其他线程(线程B的迭代器b)修改会让modCount变为2,此时线程A想要执行next方法时,由于判断了自己维护的exppectedModCount = 1 不等于实际的modCount了,就会抛出异常。

4.解决的办法,使用迭代器remove方法删除;(虽然迭代器的remove 方法中同样使用了ArrayList的remove方法,但是迭代器中多了一部分内容,其中最终要的就是在执行完成remove方法之后让expectedModCount = modCount就会使得期望值和实际值之间保持一致,即使是其他的线程修改了也不会影响)

5.总结:不允许在增强for循环使用对象直接调用remove方法删除元素,也不允许迭代器中使用对象直接调用remove方法删除元素;可以使用迭代器的remove方法删除元素才不会抛出并发修改异常;