ArrayList 并发操作 ConcurrentModificationException 异常

1、故障现象

ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常

2、故障代码

public class ArrayListTest {

    public static void main(String[] args) {

        List<String> lists = new ArrayList<>();
        lists.add("a");
        lists.add("b");

        Iterator<String> iterator = lists.iterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            if (next == "b") {
                lists.remove(next);
            }
        }
    }
}

异常截图

ArrayList 并发操作 ConcurrentModificationException 异常

3、导致原因

通过查看异常,发现异常出现的位置在 java.util.ArrayList类的内部类Itr中的checkForComodification方法中

/**
 * 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;

	Itr() {}

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

通过查看代码发现如果 modCount和expectedModCount不相等就会导致抛出异常。

modCount是修改记录数,expectedModCount是期望修改记录数,初始化时expectedModCount=modCount。

ArrayList集合的add和remove操作都有对modCount++操作,就会导致expectedModCount和modCount值不相等从而产生ConcurrentModificationException异常

4、解决方案

解决方案1:使用iterator的remove操作替代ArrayList集合自己的remove操作

public class ArrayListTest {

    public static void main(String[] args) {
        List<String> lists = new ArrayList<>();
        lists.add("a");
        lists.add("b");

        Iterator<String> iterator = lists.iterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            if (next == "b") {
                iterator.remove();
            }
        }
    }
}

分析:通过查看iterator的remove方法发现,其实还是调用了ArrayList集合的remove方法移除元素,但是会使

expectedModCount=modeCount所以不会抛出ConcurrentModificationException异常

解决方案2:使用JUC concurrent包中CopyOnWriteArrayList并发集合类

public class ArrayListTest {

    public static void main(String[] args) {
        List<String> lists = new CopyOnWriteArrayList<>();
        lists.add("a");
        lists.add("b");

        Iterator<String> iterator = lists.iterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            if (next == "b") {
                lists.remove(next);
                System.out.println(lists);
            }
        }
    }
}

因为迭代器中没有checkForComodification操作,并且集合的add和remove方法中都通过ReentrantLock加锁保证并发操作下的安全性。

5、优化建议

在对ArrayList集合进行并发操作时尽量使用CopyOnWriteArrayList集合类代替