fail-tail 机制:ConcurrentModificat

2018-07-03  本文已影响0人  博弈史密斯

对Vector、ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常。下面我们就来讨论以下这个异常出现的原因以及解决办法。

ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    list.add(i);
}

for (int i = 0; i < list.size(); i++) {
    if (i == 2) {
        list.remove(i);
    }
}

上面的代码不会有问题,看下面的代码:

//foreach,不需要下标。内部由iterator实现的
for (Integer i : list) {
    if (i == 2) {.
        list.remove(i);
    }
}

上面的代码会报错:ConcurrentModificationException

我们看一下 ArrayList 的源码,首先看下 remove:

public E remove(int index) {
    ...
    modCount++;
    ...
}

我们省略了一些代码,在 remove 中维护了 modCount值,每次调用 modCount 都会自增1.

上面我们说到,foreach会转为 iterator 实现,我们看下 iterator 的代码:

public E next() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();

    ...

    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();

    ...
}

我们看到,在 iterator 的 next 方法中,有两处抛出 ConcurrentModificationException 的地方。
第二处是数组越界,我们不关注。

我们看下第一处:modCount != expectedModCount。

expectedModCount 值是在 iterator 的add 和 remove 方法中维护:

//iterator 中的 add 方法
public void add(E e) {
    ...
    expectedModCount = modCount;
}

//iterator 中的 remove 方法
public void remove() {
    ...
    expectedModCount = modCount;
}

从上面看到,在 iterator的 add 和 remove 中 同步 expectedModCount 和 modCount。
所以,正常情况下 expectedModCount 和 modCount 都是同步的。

而在 foreach 中,调用 ArrayList 的 remove 方法,导致 modCount 加1,
而 没有调用 iterator 的 remove 方法,导致 modCount 没有同步给 expectedModCount,
在 iterator 的 next 方法中抛出异常。

所以在单线程中,有两种方式避免 ConcurrentModificationException:

  1. 在 foreach 或对iterator 做循环遍历中,通过调用 iterator 的 add 或 remove,
    让 expectedModCount 和 modCount 保持同步。
    Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()) {
        Integer integer = iterator.next();
        if (integer == 2)
            iterator.remove(); 
    }
    
  2. 不使用 iterator,通过 list 的下标进行遍历,调用 list 的 add 或 remove 方法:
    for (int i = 0; i < list.size(); i++) {
        if (i == 2) {
            list.remove(i);
        }
    }
    

多线程下,如果有一个线程通过 iterator 遍历,
另一个线程通过 List 的 remove 等进行修改,则会抛出 ConcurrentModificationException 异常:

ArrayList<Integer> list = new ArrayList<>();

private void printAll() {
    for (Integer i : list) {
        Log.i("zyb", String.valueOf(list.get(i)));
    }
}

private void removeByIndex(int index) {
    for (int i = 0; i < list.size(); i++) {
        if (i == index) {
            list.remove(i);
        }
    }
}

在 Main 方法下:

for (int i = 0; i < 100; i++) {
    list.add(i);
}

new Thread(new Runnable() {
    @Override
    public void run() {
        printAll();
    }
}).start();

new Thread(new Runnable() {
    @Override
    public void run() {
        removeByIndex(50);
    }
}).start();

解决方式 是 在 printAll、removeByIndex 两个方法前 添加 synchronized 修饰。
或者 ArrayList 改为 CopyOnWriteArrayList。

Vector 不能解决上面的问题,虽然 Vector 中 每个方法都添加了 synchronized 关键字,iterator 中的方法也添加了 synchronized 关键字,譬如 当调用 iterator 的 next 方法,next执行完,释放锁,另一个线程获得锁,调用 List 的 remove 方法,释放锁,iterator next 方法获得锁,依然会出现 modCount != expectedModCount 的问题。

使用 Vector 的时候线程并不是总是安全的,这是为什么呢?

从上面的例子 我们可以联想,就算一个类中,对每个方法 添加 synchronized ,也不能保证就是线程安全的。

上一篇下一篇

猜你喜欢

热点阅读