fail-tail 机制:ConcurrentModificat
对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。
- modCount:
是指 list对象从new出来到现在被修改次数,当调用List的add或者remove方法的时候,这个modCount都会自增; - expectedModCount:
期望的 ModCount,是指Iterator现在期望这个list被修改的次数是多少次。
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:
- 在 foreach 或对iterator 做循环遍历中,通过调用 iterator 的 add 或 remove,
让 expectedModCount 和 modCount 保持同步。Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer integer = iterator.next(); if (integer == 2) iterator.remove(); }
- 不使用 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 ,也不能保证就是线程安全的。