Java中出现java.util.ConcurrentModificationException的原理探究和解决办法
2015-02-12 12:02
204 查看
文章开始首先要感谢两位提供思路的伙伴,他们是网易博客ID为“风不可追”的伙伴,博客链接是http://fine36.blog.163.com/blog/static/189251005201258113857343/,第二位是提供了ArrayList注释翻译的伙伴,在知乎上ID为lantaozi,链接为http://www.zhihu.com/question/24086463。非常感谢他们!!!
来说一下应用场景,我拿到一个List< E >类型的供应商列表,循环该列表,将供应商code为某某某的从列表中删除,于是简化一下代码如下:
这样写就会报错,错误如下:
于是我修改了一下代码,修改后的代码如下:
这样就不报错,我就很诧异,于是开始研究原因,首先开始调试代码:
初始化的list,有5个元素A,B,C,D,E没错,size=5也对,大家注意list中modCount这个变量,执行remove语句之后:
只有B,C,D,E四个元素了,size=4了,说明remove成功,继续进入for循环,此时报错java.util.ConcurrentModificationException,按F5进行step into操作,代码如下(此时进入到ArrayList.class):
很明显,hasNext方法返回是true,进入next方法,第一行就是一个方法调用,大家注意,checkForComodification();进入到该方法:
如果modCount不等于expectedModCount的话,就抛出该异常。调试就到这里,抛出了异常,那么很明显是这两个变量不等,不等的原因在哪?继续探究,代码回溯到list.remove(s);F5进入remove方法:
传递进来的Object对象非空所以进入到else,调用了fastRemove方法删除元素,该方法第一行就对modCount进行了++操作,而初始化数组的时候,或者对数组add的时候:
try里的最后一行,是保证了modCount与expectedModCount两个变量一致的,所以说,remove(s)之后,这两个变量就不等了,抛出异常。这样做的原因是防止遍历列表的时候破坏列表结构,保证线程安全,这种情况应该是在多线程使用的时候居多。
那么问题来了,执行后面一段代码,就是使用index循环的时候怎么不抛出异常呢?我们再来回顾一下代码片段:
remove(i);删除元素之后,一样的对modCount进行了++操作,但是在for循环里边就没有checkForComodification();这个方法了,也就是说不对modCount和expectedModCount进行判断,所以就不抛出异常。这就是这两种for循环的根本区别所在。
还有另外一种循环,迭代
这种方式也不会报错,我们看看具体实现方式:
初始化迭代器的时候就保证了modCount和expectedModCount的一致性,这招很nice啊!再看remove()的时候:
也保证了这两个变量的一致性,这里更是nice,所以用迭代器的话整个过程都保证了线程同步或者说线程安全,那么就不会报错。
综上所述:
for(String s : list)这种for循环,modCount和expectedModCount不会同步修改,而在循环中会判断两个变量如果不等就抛出异常
for(int i=0;i< list.size();i++)这种for循环,modCount和expectedModCount也不会同步修改,而在循环中不会判断两个变量相等情况,所以不抛出异常
使用迭代器,无论是add操作还是remove操作,都能保证modCount和expectedModCount的一致性,所以不会抛出异常
总结:
在实际开发过程中,一开始我使用了for(int i=0;i< list.size();i++)这种for循环,结果有些元素没法删除,也不报错,实在找不到原因就换了一种for循环,就是for(String s : list)这种for循环,结果报错了,解决思路是,new一个removeList,遍历时候,removeList.add(),等到遍历结束后,在用list.removeAll(removeList)这种方法来删除。代码如下:
最后,希望能帮助大家解决一些困扰,有问题欢迎随时提出~~~
来说一下应用场景,我拿到一个List< E >类型的供应商列表,循环该列表,将供应商code为某某某的从列表中删除,于是简化一下代码如下:
List<String> list = new ArrayList<String>(); list.add("A"); list.add("B"); list.add("C"); list.add("D"); list.add("E"); for(String s : list){ if(s.equalsIgnoreCase("A")){ list.remove(s); } }
这样写就会报错,错误如下:
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859) at java.util.ArrayList$Itr.next(ArrayList.java:831) at com.param.ListTest.main(ListTest.java:18)
于是我修改了一下代码,修改后的代码如下:
for(int i=0 ; i<list.size();i++){ if(list.get(i).equalsIgnoreCase("A")){ list.remove(i); } }
这样就不报错,我就很诧异,于是开始研究原因,首先开始调试代码:
初始化的list,有5个元素A,B,C,D,E没错,size=5也对,大家注意list中modCount这个变量,执行remove语句之后:
只有B,C,D,E四个元素了,size=4了,说明remove成功,继续进入for循环,此时报错java.util.ConcurrentModificationException,按F5进行step into操作,代码如下(此时进入到ArrayList.class):
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]; }
很明显,hasNext方法返回是true,进入next方法,第一行就是一个方法调用,大家注意,checkForComodification();进入到该方法:
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
如果modCount不等于expectedModCount的话,就抛出该异常。调试就到这里,抛出了异常,那么很明显是这两个变量不等,不等的原因在哪?继续探究,代码回溯到list.remove(s);F5进入remove方法:
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } /* * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
传递进来的Object对象非空所以进入到else,调用了fastRemove方法删除元素,该方法第一行就对modCount进行了++操作,而初始化数组的时候,或者对数组add的时候:
public void add(E e) { checkForComodification(); try { int i = cursor; ArrayList.this.add(i, e); cursor = i + 1; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
try里的最后一行,是保证了modCount与expectedModCount两个变量一致的,所以说,remove(s)之后,这两个变量就不等了,抛出异常。这样做的原因是防止遍历列表的时候破坏列表结构,保证线程安全,这种情况应该是在多线程使用的时候居多。
那么问题来了,执行后面一段代码,就是使用index循环的时候怎么不抛出异常呢?我们再来回顾一下代码片段:
for(int i=0 ; i<list.size();i++){ if(list.get(i).equalsIgnoreCase("A")){ list.remove(i); } }
remove(i);删除元素之后,一样的对modCount进行了++操作,但是在for循环里边就没有checkForComodification();这个方法了,也就是说不对modCount和expectedModCount进行判断,所以就不抛出异常。这就是这两种for循环的根本区别所在。
还有另外一种循环,迭代
Iterator<String> iterator = list.iterator(); while(iterator.hasNext()){ if(iterator.next().equalsIgnoreCase("A")){ iterator.remove(); } }
这种方式也不会报错,我们看看具体实现方式:
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount;
初始化迭代器的时候就保证了modCount和expectedModCount的一致性,这招很nice啊!再看remove()的时候:
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
也保证了这两个变量的一致性,这里更是nice,所以用迭代器的话整个过程都保证了线程同步或者说线程安全,那么就不会报错。
综上所述:
for(String s : list)这种for循环,modCount和expectedModCount不会同步修改,而在循环中会判断两个变量如果不等就抛出异常
for(int i=0;i< list.size();i++)这种for循环,modCount和expectedModCount也不会同步修改,而在循环中不会判断两个变量相等情况,所以不抛出异常
使用迭代器,无论是add操作还是remove操作,都能保证modCount和expectedModCount的一致性,所以不会抛出异常
总结:
在实际开发过程中,一开始我使用了for(int i=0;i< list.size();i++)这种for循环,结果有些元素没法删除,也不报错,实在找不到原因就换了一种for循环,就是for(String s : list)这种for循环,结果报错了,解决思路是,new一个removeList,遍历时候,removeList.add(),等到遍历结束后,在用list.removeAll(removeList)这种方法来删除。代码如下:
//new一个removeList List<String> removeList = new ArrayList<String>(); for(String s : list){ if(s.equalsIgnoreCase("A")){ removeList.add(s); } } //遍历结束后,删除 list.removeAll(removeList);
最后,希望能帮助大家解决一些困扰,有问题欢迎随时提出~~~
相关文章推荐
- java.util.ConcurrentModificationException 出现的原因和解决办法
- java.util.ConcurrentModificationException 出现的原因和解决办法
- HashMap出现 java.util.ConcurrentModificationException 时的解决办法
- 出现 java.util.ConcurrentModificationException 时的解决办法
- 出现 java.util.ConcurrentModificationException 时的解决办法
- 出现 java.util.ConcurrentModificationException 时的解决办法
- java.util.ConcurrentModificationException 出现的原因和解决办法
- 出现java.util.ConcurrentModificationException 问题及解决办法
- java.util.ConcurrentModificationException 出现的原因和解决办法
- 出现 java.util.ConcurrentModificationException 时的解决办法
- java.util.ConcurrentModificationException 出现的原因和解决办法
- java.util.ConcurrentModificationException 异常解决办法及原理(顶)
- java.util.ConcurrentModificationException 异常解决办法及原理
- java.util.ConcurrentModificationException 异常解决办法及原理
- java.util.ConcurrentModificationException 解决办法(使用迭代器时出现异常)
- java.util.ConcurrentModificationException 出现的原因和解决办法
- java.util.ConcurrentModificationException 解决办法
- java.util.ConcurrentModificationException 解决办法
- 【ConcurrentModificationException】java.util.ConcurrentModificationException 解决办法
- java.util.ConcurrentModificationException 解决办法