Java深入学习07:ConcurrentModificationException异常和CopyOnWriteArrayList
一、先看一个单线程Iterator遍历读写异常
1-代码如下:创建一个ArrayList,并添加三个元素;开启1个线程,遍历该ArrayList,一边读取数据,一边删除数据
public class CopyOnWriteArrayListTest { public static void main(String[] args) { CopyOnWriteArrayListThread t = new CopyOnWriteArrayListThread(); for(int i = 0; i< 1; i++){ new Thread(t).start(); } } } class CopyOnWriteArrayListThread implements Runnable{ //方式1 public static List<String> list = new ArrayList<String>(); static{ list.add("AA"); list.add("BB"); list.add("CC"); } @Override public void run() { Iterator<String> iterator = list.iterator(); while(iterator.hasNext()){ String str = iterator.next(); System.out.println(str); if(str.equals("CC")){ list.remove(str); } } } }
2-结果如下:报ConcurrentModificationException异常
java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at juc.concurrentmap.CopyOnWriteArrayListThread.run(CopyOnWriteArrayListTest.java:42) at java.lang.Thread.run(Thread.java:748)
3-分析ConcurrentModificationException异常的原因;
在执行iterator.next()方法时,内部会先进行checkForComodification()判断,当 modCount != expectedModCount 时便抛出ConcurrentModificationException异常;
其中modCount时ArrayList被修改的次数记录,expectedModCount是ArrayList修改次数的期望值,它的初始值为modCount;
list.remove(str)操作内执行了modCount++;但是expectedModCount并额米有更新,所以报错;
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]; } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
4-单线程下的解决方案:改 list.remove(str)为iterator.remove();ArrayList中Iterator的remove方法,其中会更新expectedModCount 的值
@Override public void run() { Iterator<String> iterator = list.iterator(); while(iterator.hasNext()){ String str = iterator.next(); System.out.println(str); if(str.equals("CC")){ iterator.remove(); } } }
private class Itr implements Iterator<E> { //ArrayList中Iterator的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(); } } }
二、对于多线程,即使使用iterator.remove();还是会报ConcurrentModificationException;
1-代码如下:创建一个ArrayList,并添加三个元素;开启10个线程,遍历该ArrayList,一边读取数据,一边删除数据
public class CopyOnWriteArrayListTest { public static void main(String[] args) { CopyOnWriteArrayListThread t = new CopyOnWriteArrayListThread(); for(int i = 0; i< 10; i++){ new Thread(t).start(); } } } class CopyOnWriteArrayListThread implements Runnable{ //方式1 public static List<String> list = new ArrayList<String>(); static{ list.add("AA"); list.add("BB"); list.add("CC"); } @Override public void run() { Iterator<String> iterator = list.iterator(); while(iterator.hasNext()){ String str = iterator.next(); System.out.println(str); if(str.equals("CC")){ iterator.remove(); } } } }
异常日志
java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at juc.concurrentmap.CopyOnWriteArrayListThread.run(CopyOnWriteArrayListTest.java:46) at java.lang.Thread.run(Thread.java:748)
2-分析问题:原因其实还是 modCount != expectedModCount 导致;可以这么理解modCount 参数属于list对象,即10个线程公用一个 modCount ;但 expectedModCount 属于list的 Iterator 对象,而list的 Iterator 对象在每一个新线程中,都会重新创建;则每个线程的expectedModCount 都是相互独立的,势必导致"公有变量" modCount 不等于 expectedModCount 的情况。
3-解决方案:使用CopyOnWriteArrayList记录列表数据,并且删除数据时,使用list.remove(str);
class CopyOnWriteArrayListThread implements Runnable{ //方式2 public static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); static{ list.add("AA"); list.add("BB"); list.add("CC"); } @Override public void run() { Iterator<String> iterator = list.iterator(); while(iterator.hasNext()){ String str = iterator.next(); System.out.println(str); if(str.equals("CC")){ list.remove(str); } } } }
三、有关CopyOnWriteArrayList
1-CopyOnWriteArrayList是什么?
根据官方的注解:CopyOnWriteArrayList是线程安全的ArrayList,其对应的add、remove等修改操作会同时创建一个新的副本
2-CopyOnWriteArrayList如何做到线程安全
add()和remove():CopyOnWriteArrayList类最大的特点就是,在对其实例进行修改操作(add/remove等)会新建一个数据并修改,修改完毕之后,再将原来的引用指向新的数组。这样,修改过程没有修改原来的数组。也就没有了ConcurrentModificationException错误。
//CopyOnWriteArrayList中的add方法 public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } } //CopyOnWriteArrayList中的remove方法,调用下面的 remove(o, snapshot, index) public boolean remove(Object o) { Object[] snapshot = getArray(); int index = indexOf(o, snapshot, 0, snapshot.length); return (index < 0) ? false : remove(o, snapshot, index); } //真正的remove方法 private boolean remove(Object o, Object[] snapshot, int index) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] current = getArray(); int len = current.length; if (snapshot != current) findIndex: { int prefix = Math.min(index, len); for (int i = 0; i < prefix; i++) { if (current[i] != snapshot[i] && eq(o, current[i])) { index = i; break findIndex; } } if (index >= len) return false; if (current[index] == o) break findIndex; index = indexOf(o, current, index, len); if (index < 0) return false; } Object[] newElements = new Object[len - 1]; System.arraycopy(current, 0, newElements, 0, index); System.arraycopy(current, index + 1, newElements, index, len - index - 1); setArray(newElements); return true; } finally { lock.unlock(); } }
get()
//CopyOnWriteArrayList中的get方法 public E get(int index) { final ReentrantLock lock = l.lock; lock.lock(); try { rangeCheck(index); checkForComodification(); return l.get(index+offset); } finally { lock.unlock(); } }