单线程并发修改异常
代码如下:
public class CopyOnWriteTest {public static void main(String[] args) {List<Integer> list = new ArrayList<>();list.add(1);list.add(2);list.add(3);Iterator<Integer> iterator = list.iterator();while (iterator.hasNext()) {Integer i = iterator.next();if(i == 1) {list.remove(i);}}}}
有异常抛出:
Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)at java.util.ArrayList$Itr.next(ArrayList.java:859)at Thread.CopyOnWriteTest.main(CopyOnWriteTest.java:30)Process finished with exit code 1
此异常出在迭代器上。
大体来说是因为当执行next()方法的时候会有一个判断,如果modCount != expectedModCount,就会抛出此异常,初始时这两个是相等的,一旦对集合进行更新操作,modCount就会加一,所以这两个就不相等了,就会抛出此异常。
涉及到的源码如下:
int expectedModCount = modCount;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];}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 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}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
- 我们从
remove()方法开始,进入到第16行,我们要移除1,进入到第24行,遍历list集合,找到1之后,通过fastRemove()方法移除掉1. - 进入
fastRemove()方法,第33行,刚进去他就把modCount加一,我们看第1行,这两个值初始是相等的,现在不相等了。删除1之后,我们继续向下遍历。 - 执行
iterator.next()方法,进入到第5行,先运行checkForComodification()进行检查。 - 检查,进入到第
42行,判断两个数据是否相等,因为刚才的删除操作使得modCount加一,现在这两个数不相等了,所以抛出异常。 
问:为什么要抛出此异常,或者说,为什么遍历的时候要先对比**modCount和expectedModCount
答:对于ArrayList这种线程不安全的集合来说,当多个线程对集合进行修改和读取操作时,往往会造成线程安全问题。**
源码中对参数modCount是这么解释的:
/*** The number of times this list has been <i>structurally modified</i>.* Structural modifications are those that change the size of the* list, or otherwise perturb it in such a fashion that iterations in* progress may yield incorrect results.** <p>This field is used by the iterator and list iterator implementation* returned by the {@code iterator} and {@code listIterator} methods.* If the value of this field changes unexpectedly, the iterator (or list* iterator) will throw a {@code ConcurrentModificationException} in* response to the {@code next}, {@code remove}, {@code previous},* {@code set} or {@code add} operations. This provides* <i>fail-fast</i> behavior, rather than non-deterministic behavior in* the face of concurrent modification during iteration.** <p><b>Use of this field by subclasses is optional.</b> If a subclass* wishes to provide fail-fast iterators (and list iterators), then it* merely has to increment this field in its {@code add(int, E)} and* {@code remove(int)} methods (and any other methods that it overrides* that result in structural modifications to the list). A single call to* {@code add(int, E)} or {@code remove(int)} must add no more than* one to this field, or the iterators (and list iterators) will throw* bogus {@code ConcurrentModificationExceptions}. If an implementation* does not wish to provide fail-fast iterators, this field may be* ignored.*/protected transient int modCount = 0;
单线程解决并发修改异常
把list.remove(1)改成iterator.remove()
即使用Itr中的remove()方法,如下:
public void remove() {if (lastRet == -1)throw new IllegalStateException();checkForComodification();try {AbstractList.this.remove(lastRet);if (lastRet < cursor)cursor--;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException e) {throw new ConcurrentModificationException();}}
因为第11行,他把两个参数设置成相等了,这样就能继续遍历了。
多线程并发修改异常
代码如下:
public class CopyOnWriteTest {public static void main(String[] args) {List<String> list = new ArrayList<>();for (int i = 0; i < 10; i++) {new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,2));System.out.println(Thread.currentThread().getName()+":"+list);},String.valueOf(i)).start();}}}
代码中,有10个线程,每一个线程都对集合进行增加和遍历操作。也会出ConcurrentModificationException异常,原因和单线程的解释方法一样,当增加操作导致modCount和expectedModCoun时,遍历的时候就会抛出异常,至于代码中的迭代器,第7行直接System.out.println(list),就可以使用迭代器遍历,因为list后面省略了toString()方法,而ArrayList的toString()方法是在AbstractCollection里,而ArrayList继承了AbstractList,AbstractList继承了AbstractCollection。
方法如下:
public String toString() {Iterator<E> it = iterator();if (! it.hasNext())return "[]";StringBuilder sb = new StringBuilder();sb.append('[');for (;;) {E e = it.next();sb.append(e == this ? "(this Collection)" : e);if (! it.hasNext())return sb.append(']').toString();sb.append(',').append(' ');}}
可以看到,toString方法就是使用迭代器对集合进行迭代。
多线程解决并发修改异常
用JUC下的类来代替,比如用CopyOnWriteArrayList来代替ArrayList。
