1)介绍
并发修改ConcurrentModificationException错误是开发中一个常见错误,多发生在对一个Collection边遍历边做影响size变化的操作中,下面以ArrayList为例分析ConcurrentModificationException错误。

2)分析
ArrayList初始数据如下:

1         List<Integer> list = new ArrayList<Integer>(); 
2         list.add(1); 
3         list.add(2); 
4         list.add(3);

【场景1】不会有并发修改错误

1         int length = list.size(); 
2         for (int i = 0; i < length; i++) { 
3             if (list.get(i).equals(2)) { 
4                 list.add(10); 
5             } 
6         }

【场景2】会有并发修改错误

1         for(int temp : list) { 
2             if(temp == 2) { 
3                 list.add(10); 
4             } 
5         }

【场景3】会有并发修改错误

1         Iterator<Integer> iterator = list.iterator(); 
2         while(iterator.hasNext()) { 
3             if(iterator.next().equals(2)) { 
4                 list.add(10); 
5             } 
6         }

【场景4】没有并发修改问题

1         ListIterator<Integer> listIterator = list.listIterator(); 
2         while (listIterator.hasNext()) { 
3             if (listIterator.next().equals(2)) { 
4                 listIterator.add(10); 
5             } 
6         }

【分析】

  其实ConcurrentModificationException异常的抛出是由于checkForComodification(AbstractList类中)方法的调用引起的。

1     private void checkForComodification() { 
2         if (this.modCount != l.modCount) 
3             throw new ConcurrentModificationException(); 
4     }

  而checkForComodification方法的调用发生在Iterator相关api方法中,在调用list的iterator方法会创建一个Itr对象。

  在创建会与AbstractList的modCount赋予相同的值, 而在Itr的next方法中会调用checkForComodification

  在场景3中,list.add操作更改modCount的值,所以会有并发修改错误,而场景1中并没有使用iterator相关api,add操作虽然修改了modCount但是不会检查modCount所以没有并发修改错误。 
  场景4中,ListItr类add方法

 1         public void add(E e) { 
 2             checkForComodification(); 
 3             try { 
 4                 int i = cursor; 
 5                 ArrayList.this.add(i, e); 
 6                 cursor = i + 1; 
 7                 lastRet = -1; 
 8                 expectedModCount = modCount; 
 9             } catch (IndexOutOfBoundsException ex) { 
10                 throw new ConcurrentModificationException(); 
11             } 
12         }

  其中8行:在调用了list.add操作之后,将ListItr中的expectedModCount与AbstractList中的modCount进行了同步,所以在下次调用next也就不会抛出异常了,此时假如以后不调用next或者又重新创建了 ListItr也不会有异常抛出。 
  最后场景2并没有使用Iterator中的api为什么也抛出了异常了。其实编译器会将for-each循环代码编译为Iterator相关api的调用。为了便于查看编译后的代码这里添加一个“———-”打印。

 1         List<Integer> list = new ArrayList<Integer>(); 
 2         list.add(1); 
 3         list.add(2); 
 4         list.add(3); 
 5         System.out.println("------------"); 
 6         for (int temp : list) { 
 7             if (temp == 2) { 
 8                 list.add(10); 
 9             } 
10         }

编译后的字节码为:

 

所以场景2和场景3是一样的,也会抛出异常了。


发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

List接口相对于Collection接口的特有方法知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。