本文原创地址,
我的博客
:https://jsbintask.cn/2019/04/09/jdk/jdk8-concurrentmodificationexception/(食用效果最佳),转载请注明出处!
前言
ConCurrentModificationException
是jdk用于限制并发情况下容器结构改变的异常类。当一个线程操作一个容器时,此时如果有另一个线程修改了容器大小,将抛出这个异常,我们看下面两段代码
Code 2::说明
:一个线程进行list的排序操作,一个线程移除list中的元素,结果:
上面代码说明不管是单线程情况下还是多线程并发运行模式下,一旦在某些情况下(如上面的遍历,排序),容器的结构一旦被修改就将抛出ConCurrentModificationException
。
源码解析
为什么会这样呢,这样是不是就代表这些容器是线程安全的呢?
我们通过源码来讨论一下。
首先第一个例子,一个线程进行遍历操作:for(T t: Collections)
,通过查看字节码知道,它其实就是使用了Iterator
进行了遍历操作:
接着查看ArrayList中的Iterator发现它内部是这么定义的:
ArrayList内部有一个modCount
成员变量,每次就行修改操作如增加,删除等会增加该值:
而当初始化一个Iterator时,会记录当前的modCount,以后每次操作(next(), remove())都会检查该值:
一旦和记录的初始值不相等,则会抛出异常!
同理,上面的排序操作sort()
方法同样是这么定义的:
这样我们上面的疑问就解开了,之所以会抛出异常,是因为容器内部维护了一个变量modCount
,在进行某些操作时(iterator,sort)时,会记录当时的这个值,在操作过程中这个值一旦发生改变则会抛出ConcurrentModificationException。
在jdk
中,这种行为被称为快速失败
,它的目的是为了尽最大努力的检测线程安全!但是! 它并不能保证容器的线程安全
!
这个例子中,我们使用多线程添加了10000个元素,最后成功添加的却只有9993个元素,说明它内部并没有保证线程安全! 当我们在并发情况下使用这些容器时就需要考虑线程安全问题,替换线程安全的容器类(如ConcurrentHashMap, Vector, CopyOnWriteArrayList等
)或者使用额外的同步手段如加锁!
扩展
在单线程遍历时,如果想删除某个元素,可以使用iterator.remove()
:
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add("list->" + (i + 1));
}
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next.equals("list->3")) {
iterator.remove();
}
System.out.println(next);
}
System.out.println(list);
对于ArrayList而言,你也可以调用lsitIterator()
方法获取内部的ListIterator
从而进行添加,插入操作:
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add("list->" + (i + 1));
}
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next.equals("list->3")) {
iterator.remove();
}
System.out.println(next);
iterator.add("list->7");
}
System.out.println(list);
关注我,这里只有干货!