单线程并发修改异常

代码如下:

  1. public class CopyOnWriteTest {
  2. public static void main(String[] args) {
  3. List<Integer> list = new ArrayList<>();
  4. list.add(1);
  5. list.add(2);
  6. list.add(3);
  7. Iterator<Integer> iterator = list.iterator();
  8. while (iterator.hasNext()) {
  9. Integer i = iterator.next();
  10. if(i == 1) {
  11. list.remove(i);
  12. }
  13. }
  14. }
  15. }

有异常抛出:

  1. Exception in thread "main" java.util.ConcurrentModificationException
  2. at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
  3. at java.util.ArrayList$Itr.next(ArrayList.java:859)
  4. at Thread.CopyOnWriteTest.main(CopyOnWriteTest.java:30)
  5. Process finished with exit code 1

此异常出在迭代器上。
大体来说是因为当执行next()方法的时候会有一个判断,如果modCount != expectedModCount,就会抛出此异常,初始时这两个是相等的,一旦对集合进行更新操作,modCount就会加一,所以这两个就不相等了,就会抛出此异常。
涉及到的源码如下:

  1. int expectedModCount = modCount;
  2. public E next() {
  3. checkForComodification();
  4. int i = cursor;
  5. if (i >= size)
  6. throw new NoSuchElementException();
  7. Object[] elementData = ArrayList.this.elementData;
  8. if (i >= elementData.length)
  9. throw new ConcurrentModificationException();
  10. cursor = i + 1;
  11. return (E) elementData[lastRet = i];
  12. }
  13. public boolean remove(Object o) {
  14. if (o == null) {
  15. for (int index = 0; index < size; index++)
  16. if (elementData[index] == null) {
  17. fastRemove(index);
  18. return true;
  19. }
  20. } else {
  21. for (int index = 0; index < size; index++)
  22. if (o.equals(elementData[index])) {
  23. fastRemove(index);
  24. return true;
  25. }
  26. }
  27. return false;
  28. }
  29. private void fastRemove(int index) {
  30. modCount++;
  31. int numMoved = size - index - 1;
  32. if (numMoved > 0)
  33. System.arraycopy(elementData, index+1, elementData, index,
  34. numMoved);
  35. elementData[--size] = null; // clear to let GC do its work
  36. }
  37. final void checkForComodification() {
  38. if (modCount != expectedModCount)
  39. throw new ConcurrentModificationException();
  40. }
  1. 我们从remove()方法开始,进入到第16行,我们要移除1,进入到第24行,遍历list集合,找到1之后,通过fastRemove()方法移除掉1.
  2. 进入fastRemove()方法,第33行,刚进去他就把modCount加一,我们看第1行,这两个值初始是相等的,现在不相等了。删除1之后,我们继续向下遍历。
  3. 执行iterator.next()方法,进入到第5行,先运行checkForComodification()进行检查。
  4. 检查,进入到第42行,判断两个数据是否相等,因为刚才的删除操作使得modCount加一,现在这两个数不相等了,所以抛出异常。

问:为什么要抛出此异常,或者说,为什么遍历的时候要先对比**modCountexpectedModCount
答:对于ArrayList这种线程不安全的集合来说,当多个线程对集合进行修改和读取操作时,往往会造成线程安全问题。**
源码中对参数modCount是这么解释的:

  1. /**
  2. * The number of times this list has been <i>structurally modified</i>.
  3. * Structural modifications are those that change the size of the
  4. * list, or otherwise perturb it in such a fashion that iterations in
  5. * progress may yield incorrect results.
  6. *
  7. * <p>This field is used by the iterator and list iterator implementation
  8. * returned by the {@code iterator} and {@code listIterator} methods.
  9. * If the value of this field changes unexpectedly, the iterator (or list
  10. * iterator) will throw a {@code ConcurrentModificationException} in
  11. * response to the {@code next}, {@code remove}, {@code previous},
  12. * {@code set} or {@code add} operations. This provides
  13. * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
  14. * the face of concurrent modification during iteration.
  15. *
  16. * <p><b>Use of this field by subclasses is optional.</b> If a subclass
  17. * wishes to provide fail-fast iterators (and list iterators), then it
  18. * merely has to increment this field in its {@code add(int, E)} and
  19. * {@code remove(int)} methods (and any other methods that it overrides
  20. * that result in structural modifications to the list). A single call to
  21. * {@code add(int, E)} or {@code remove(int)} must add no more than
  22. * one to this field, or the iterators (and list iterators) will throw
  23. * bogus {@code ConcurrentModificationExceptions}. If an implementation
  24. * does not wish to provide fail-fast iterators, this field may be
  25. * ignored.
  26. */
  27. protected transient int modCount = 0;

单线程解决并发修改异常

list.remove(1)改成iterator.remove()
即使用Itr中的remove()方法,如下:

  1. public void remove() {
  2. if (lastRet == -1)
  3. throw new IllegalStateException();
  4. checkForComodification();
  5. try {
  6. AbstractList.this.remove(lastRet);
  7. if (lastRet < cursor)
  8. cursor--;
  9. lastRet = -1;
  10. expectedModCount = modCount;
  11. } catch (IndexOutOfBoundsException e) {
  12. throw new ConcurrentModificationException();
  13. }
  14. }

因为第11行,他把两个参数设置成相等了,这样就能继续遍历了。

多线程并发修改异常

代码如下:

  1. public class CopyOnWriteTest {
  2. public static void main(String[] args) {
  3. List<String> list = new ArrayList<>();
  4. for (int i = 0; i < 10; i++) {
  5. new Thread(()->{
  6. list.add(UUID.randomUUID().toString().substring(0,2));
  7. System.out.println(Thread.currentThread().getName()+":"+list);
  8. },String.valueOf(i)).start();
  9. }
  10. }
  11. }

代码中,有10个线程,每一个线程都对集合进行增加和遍历操作。也会出ConcurrentModificationException异常,原因和单线程的解释方法一样,当增加操作导致modCountexpectedModCoun时,遍历的时候就会抛出异常,至于代码中的迭代器,第7行直接System.out.println(list),就可以使用迭代器遍历,因为list后面省略了toString()方法,而ArrayList的toString()方法是在AbstractCollection里,而ArrayList继承了AbstractListAbstractList继承了AbstractCollection
方法如下:

  1. public String toString() {
  2. Iterator<E> it = iterator();
  3. if (! it.hasNext())
  4. return "[]";
  5. StringBuilder sb = new StringBuilder();
  6. sb.append('[');
  7. for (;;) {
  8. E e = it.next();
  9. sb.append(e == this ? "(this Collection)" : e);
  10. if (! it.hasNext())
  11. return sb.append(']').toString();
  12. sb.append(',').append(' ');
  13. }
  14. }

可以看到,toString方法就是使用迭代器对集合进行迭代。

多线程解决并发修改异常

JUC下的类来代替,比如用CopyOnWriteArrayList来代替ArrayList