• 欢迎访问天天编码网站,Java技术、技术书单、开发工具,欢迎加入天天编码
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏天天编码吧
  • 我们的淘宝店铺已经开张了哦,传送门:https://shop145764801.taobao.com/

深入分析java.util.ConcurrentModificationException

Java基础 tiantian 2352次浏览 0个评论 扫描二维码

如果读者有着实际的Java项目编程经验,那么很可能遭遇过 java.util.ConcurrentModificationException 这个报错信息。如果读者足够细心的话,可能可以发现该错误信息的发生,经常是在使用 for循环 遍历集合的过程中。本文将带着读者来分析和解决这个常见的错误。实际上,准确的错误信息可能如下所示:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
...
...

深入分析java.util.ConcurrentModificationException

问题复现

ArrayList作为Java程序中最常被使用的集合类型,使用迭代方式遍历该集合,并删除其中的某些特定元素是一个很常见的操作场景。我们直接看来示例代码:

public class AddRemoveListElement {

public static void main(String args[]) {
List list = new ArrayList();
list.add("天天");
list.add("编码");

for (String s : list) {
if (s.equals("编码")) {
list.remove(s);
}
}
}
}

此代码编译正确,运行时却会抛出如下的错误信息:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at arraylist.AddRemoveListElement.main(AddRemoveListElement.java:17)

解决方案一

我们可以使用 Iterator遍历 代替 for循环 ,从而解决此问题。在 Iterator 遍历的过程中,允许使用 remove()方式去删除底层支撑的集合中的元素。

Iterator iter = list.iterator();
while(iter.hasNext()){
String str = iter.next();
if( str.equals("B") )
{
iter.remove();
}
}

此方案的背后的逻辑比较复杂,我总结出以下几个要点,希望可以帮助理解:
1. for循环只是Java的语法糖,底层还是会使用 Iterator 方式。
2. ArrayList的实现细节中,有一个记录ArrayList底层数组修改次数的计数器
3. ArrayList的remove方法会修改计数器的值
4. Iterator的remove方法会修正计数器的值
4. Iterator 迭代的过程会检查计数器的值

解决方案二

如果不使用 ArrayList, 而是使用 CopyOnWriteArrayList, 那么一样可以解决此错误。CopyOnWriteArrayList 是ArrayList的一个线程安全的变种,它的每次修改自身的操作(add, set, remove)都会在拷贝底层数组,从而获得一个全新的数组。

public static void main(String args[]) {
List list = new CopyOnWriteArrayList();
list.add("A");
list.add("B");

for (String s : list) {
if (s.equals("B")) {
list.remove(s);
}
}
}

当然了,CopyOnWriteArrayList 由于每次修改操作都需要拷贝获取新的底层数组,性能上会低于ArrayList。

思维扩展

前面,我们演示了ArrayList的问题和两个解决方案,那么同为JDK中LinkedList和HashSet等其他的Collection类似是否也存在相同的问题呢?我们来看两个实例代码:

public static void main(String args[]) {
Set set = new HashSet();
set.add("天天");
set.add("编码");

for (String s : set) {
if (s.equals("编码")) {
set.remove(s);
}
}
}
public static void main(String args[]) {
LinkedList llist = new LinkedList();
llist.add("天天");
llist.add("编码");

for (String s : llist) {
if (s.equals("编码")) {
llist.remove(s);
}
}
}

建议大家上机验证,这两个实例代码的运行结果与具体的 JDK 的版本有关。具体而言,需要参考 ArrayList、HashSet 和 LinkedList 这三个类的内部迭代器 Iterator 类的 hasNext() 方法的实现细节。我们来分别看看在 JDK 1.7 中这三个类的 Iterator 类的 hasNext() 方法的实现细节:

// ArrayList
private class Itr implements Iterator {
int cursor;       // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;

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];
}
...
}
// LinkedList
private class ListItr implements ListIterator {
private Node lastReturned;
private Node next;
private int nextIndex;
private int expectedModCount = modCount;

ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}

public boolean hasNext() {
return nextIndex < size;
}

public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();

lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
...
}
// HashSet
abstract class HashIterator {
Node<K,V> next;        // next entry to return
Node<K,V> current;     // current entry
int expectedModCount;  // for fast-fail
int index;             // current slot

HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}

public final boolean hasNext() {
return next != null;
}

final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
...
}

天天编码 , 版权所有丨本文标题:深入分析java.util.ConcurrentModificationException
转载请保留页面地址:http://www.tiantianbianma.com/java-concurrentmodificationexception.html/
喜欢 (3)
支付宝[多谢打赏]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址