HashMap中关于这点的说明
<p>The iterators returned by all of this class's "collection view methods"
are <i>fail-fast</i>: if the map is structurally modified at any time after
the iterator is created, in any way except through the iterator's own
<tt>remove</tt> method, the iterator will throw a
{@link ConcurrentModificationException}. Thus, in the face of concurrent
modification, the iterator fails quickly and cleanly, rather than risking
arbitrary, non-deterministic behavior at an undetermined time in the
future.
<p>Note that the fail-fast behavior of an iterator cannot be guaranteed
as it is, generally speaking, impossible to make any hard guarantees in the
presence of unsynchronized concurrent modification. Fail-fast iterators
throw <tt>ConcurrentModificationException</tt> on a best-effort basis.
Therefore, it would be wrong to write a program that depended on this
exception for its correctness: <i>the fail-fast behavior of iterators
should be used only to detect bugs.</i>
如果在使用iterator(或者foreach,它实质上就是iterator)遍历元素时通过源列表直接删除元素会导致ConcurrentModificationException,jdk的设计者本意是fast-fail
1.用来对出现并发问题时提前报错,防止问题扩散难以查找原因
2.禁止在遍历时在外部修改源集合导致数据不一致问题
,这个是错误使用
下面这个就是2的具体表现
public class ConcurrentModifyExcetionTest {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
Iterator<Integer> itr = list.iterator();
while (itr.hasNext()) {
Integer element = itr.next();
list.remove(element);
}
}
}
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.github.alonwang.other.ConcurrentModifyExcetionTest.main(ConcurrentModifyExcetionTest.java:18)
下面通过源码分析
list.iterator()
,可以看到返回了AbstractList的一个内部类Itr(PS.这里有个面向对象的常识,如果本类中找不到对应的方法,就去父类/接口中找)
public Iterator<E> iterator() {
return new Itr();
}
Itr
,它是迭代器的一个内部实现,它最重要的字段就是expectedModCount
- expectedModCount 预期被修改的次数,属于Itr
私有
,初始时和modCount相等 - modCount 集合被结构性修改(新增或删除)的次数,它是属于集合的
使用itr进行遍历/删除时都会进行checkForComodification()检查,只有在使用itr的remove()来移除元素,expectedModCount才会被更新
,如果通过源集合直接删除,modCount会更新expectedModCount不会发生变化,也就导致modCount!=expectedModCount进而报错
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
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();
}
}
对于单线程程序,正确的用法是这样的
public class ConcurrentModifyExcetionTest {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
Iterator<Integer> itr = list.iterator();
while (itr.hasNext()) {
Integer element = itr.next();
//使用itr的remove方法来移除
itr.remove();
}
}
}
这有引出另外一个问题,多线程下使用itr.remove()还会出现ConcurrentModificationException吗?
答案是 会的
.问题的核心在于itr是线程私有的,这隐含着expectedModCount也是线程私有的.而modCount是线程共享的.
如果有一个线程对集合进行了结构性修改,那么modCount和此线程的expectedModCount会更新,其他线程的expectedModCount都不会更新,也就势必导致其他线程的expectedModCount!=modCount
,最终导致ConcurrentModificationException
总结
ConcurrentModificationException是集合对并发的一种自我防御机制,它通过预先检查提前报错来防止更严重的问题,如果遇到这个问题要思考一下原因
- 自己使用错误
- 需要换用线程安全的集合如CopyOnWriteArrayList
网友评论