这个寒假看的《Java并发编程实战》终于派上了用场,今天搬砖过程中遇到了ConcurrentModificationException,结果还是不会。之后晚上回去恶补一波,写此文章。

JCP -> 《Java并发编程实战》

Java并发-目录

Single Thread

JCP 第五章:在单线程代码中也可能抛出ConcurrentModificationException。当对象直接从容器中删除而不是通过Iterator.remove来删除时,就会抛出这个异常。

虽然ConcurrentModificationException这个异常听上去是多线程环境下出现的。但是如上面的引用,单线程里如果使用不当也会出现。

  private final ArrayList<Integer> list = new ArrayList<>();
  private void iterateAndRemove() {
    for (Integer i : list) {
      if (i % 4 == 0) {
        list.remove(i);
      }
    }
  }

在使用for-each遍历列表时,实际上就是用Iterator遍历,但在删除的时候使用了list.remove(),这就造成了ArrayList中的modCountIterator中的modCount不相同,从而抛出异常。

解决办法也很简单,只要使用Iterator.remove就不会有异常。所以即使在单线程中也不能混用Iterator和容器中的方法。

Multi Thread

多线程的情况下,情况变得更加复杂。我们来看下面的实例代码:

public class ConcurrentModificationExceptionTest {

  private final ExecutorService executor = Executors.newFixedThreadPool(10);
  private final ArrayList<Integer> list = new ArrayList<>();

  // works
  private final Runnable iteratorRunnable = () -> {
    Iterator<Integer> it = list.iterator();
    while (it.hasNext()) {
      int i = it.next();
      if (i % 4 == 0) {
        it.remove();
        System.out.println("Removed " + i);
      }
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  };
  
  public ConcurrentModificationExceptionTest() {
    for (int i = 0; i < 100; i++) {
      list.add(i);
    }
  }

  public void testArrayList() {
    for (int i = 0; i < 20; i++) {
      executor.execute(iteratorRunnable);
      executor.execute(() -> {
        for (Integer integer : list) {
          System.out.println(integer);
          try {
            Thread.sleep(100);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      });
    }
  }

  public void stop() throws InterruptedException {
    executor.shutdown();
    executor.awaitTermination(5, TimeUnit.SECONDS);
  }

  public static void main(String[] args) throws InterruptedException {
    ConcurrentModificationExceptionTest test = new ConcurrentModificationExceptionTest();
    test.testArrayList();
    test.stop();
  }
}

上面的代码也会造成ConcurrentModificationException,因为多个线程同时遍历列表,也可能造成ArrayList中的modCountIterator中的modCount不相同。

Solution

  • 迭代时对容器加锁
synchronized (list) {
  for (Integer integer : list) {
		//doSomething
  }
}
  • 在副本上进行迭代
ArrayList<Integer> cloneList = (ArrayList<Integer>) list.clone();
for (Integer integer : cloneList) {
	//doSomething
}
  • 使用CopyOnWriteArrayList

参考