1. 什么是 ConcurrentModificationException?
大家都听说过快速报错 fast-fail 吧, fast-fail 的发生就是说明发生了 ConcurrentModificationException 异常其实发生这种异常的事件有两种, 一种是在 Iterator 在迭代过程中, 出现删除或者增加 Collection 中的元素的时候另一种是多线程情况下, 一个线程在用 Iterator 迭代 Collection 元素时, 另一个线程却在给这个 Collection 增加或者删除元素大家也许看出来了, 两种情况其实都是在 Iterator 迭代元素过程中增加或者删除元素
2. 制造 ConcurrentModificationException?
- /**
- * Cme 表示 ConcurrentModificationException
- * 目的是制造 ConcurrentModificationException
- */
- public class Cme implements Runnable {
- List list = new ArrayList();@Override public void run() {
- list.add(1);
- list.add(2);
- Iterator itr = list.listIterator();
- while (itr.hasNext()) {
- System.out.println(itr.next());
- list.add(3);
- }
- }
- public static void main(String[] args) throws InterruptedException {
- Cme cm = new Cme();
- Thread thread_1 = new Thread(cm);
- thread_1.start();
- }
- }
运行结果:
- 1
- Exception in thread "Thread-0" java.util.ConcurrentModificationException
- at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
- at java.util.ArrayList$Itr.next(ArrayList.java:851)
- at dp.Cme.run(Cme.java:17)
- at java.lang.Thread.run(Thread.java:745)
确实是发生错误了, 点击错误信息, 调到了下面的代码:
- final void checkForComodification() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- }
大概意思是 Collection 中的元素与开始遍历的时候传来的个数不相同
那到底是什么调用了上面的方法?
- public E next() {
- checkForComodification();
- int i = cursor;
- if (i >= SubList.this.size)
- throw new NoSuchElementException();
- Object[] elementData = ArrayList.this.elementData;
- if (offset + i >= elementData.length)
- throw new ConcurrentModificationException();
- cursor = i + 1;
- return (E) elementData[offset + (lastRet = i)];
- }
那我们在 next()方法后面, 仿照 CAS 机制中的 ABA 问题, 先添加元素然后删除元素, 行不行呢?(这样做毫无意义)
答案是不行的:
- public void remove() {
- if (lastRet < 0) throw new IllegalStateException();
- checkForComodification();
- try {
- SubList.this.remove(lastRet);
- cursor = lastRet;
- lastRet = -1;
- expectedModCount = ArrayList.this.modCount;
- } catch(IndexOutOfBoundsException ex) {
- throw new ConcurrentModificationException();
- }
- }
因为 remove 中也调用了上面的那个方法但是我们可以用一种方法, 在遍历的过程中把需要删除的对象保存到一个集合中, 等遍历结束后再调用 removeAll()方法来删除, 或者使用 iterator.remove()方法
3. 为什么我们需要 ConcurrentHashMap 和 CopyOnWriteArrayList?
同步的集合类 (Hashtable 和 Vector), 同步的封装类(使用 Collections.synchronizedMap() 方法和 Collections.synchronizedList()方法返回的对象)可以创建出线程安全的 Map 和 List 但是有些因素使得它们不适合高并发的系统它们仅有单个锁, 对整个集合加锁, 以及为了防止 ConcurrentModificationException 异常经常要在迭代的时候要将集合锁定一段时间, 这些特性对可扩展性来说都是障碍
4. 为什么用 CopyOnWriteArrayList 代替 ArrayList, 就不会产生 ConcurrentModificationException?
从字面意思上可以看出来复制出来一份来操作, 然后写道原先的 ArrayList 中这样说肯定不行, 我们看看源码, 验证字面意思对不对?
既然在迭代的时候能 add, 我们先看看 add 的实现:
- /**
- *1 获取互斥锁
- *2Copy 当前元素数组创建新数组, 数组内存空间增 1
- *3 添加元素
- *4 请求容器数据数组内存地址变更为新数组地址
- *5 释放互斥锁
- *6 返回结果
- **/
- 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();
- }
- }
那还有一个问题, 在调用 Iterator 的 next 方法, 结果会是什么样的呢? 边 add 边迭代吗?
- /**
- * Cme 表示 ConcurrentModificationException
- * 目的是消除 ConcurrentModificationException
- */
- public class Cme2 implements Runnable{
- List list = new CopyOnWriteArrayList();
- @Override
- public void run() {
- list.add(1);
- list.add(2);
- Iterator itr = list.listIterator();
- while (itr.hasNext()) {
- System.out.println(itr.next());
- list.add(3);
- }
- }
- public static void main(String[] args) throws InterruptedException {
- Cme2 cm = new Cme2();
- Thread thread_1 = new Thread(cm);
- thread_1.start();
- }
- }
运行结果:
1 2
看看原因:
- public Iterator < E > iterator() {
- return new COWIterator < E > (getArray(), 0);
- }
- static final class COWIterator<E> implements ListIterator<E> {
- /** Snapshot of the array */
- private final Object[] snapshot;
- /** Index of element to be returned by subsequent call to next. */
- private int cursor;
- private COWIterator(Object[] elements, int initialCursor) {
- cursor = initialCursor;
- snapshot = elements;
- }
- public boolean hasNext() {
- return cursor < snapshot.length;
- }
- public boolean hasPrevious() {
- return cursor > 0;
- }
- @SuppressWarnings("unchecked")
- public E next() {
- if (! hasNext())
- throw new NoSuchElementException();
- return (E) snapshot[cursor++];
- }
- }
并发场景下对容器的添加操作是通过在容器内部数据数组的副本来完成的对容器的迭代使用的是容器原始数据数组因为迭代不会产生修改, 因此多个线程可以同时对容器进行迭代, 而不会对彼此干扰或影响修改容器的线程
5. 为什么用 ConcurrentHashMap 代替 hashMap, 就不会产生 ConcurrentModificationException?
- /**
- * Cme 表示 ConcurrentModificationException
- * 目的是制造 ConcurrentModificationException
- */
- public class Cme implements Runnable{
- Map list = new HashMap();
- @Override
- public void run() {
- list.put('1',1);
- list.put('2',2);
- Iterator itr = list.values().iterator();
- while (itr.hasNext()) {
- System.out.println(itr.next());
- list.put('3',3);
- }
- }
- public static void main(String[] args) throws InterruptedException {
- Cme cm = new Cme();
- Thread thread_1 = new Thread(cm);
- thread_1.start();
- }
- }
运行结果:
- 1
- Exception in thread "Thread-0" java.util.ConcurrentModificationException
- at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
- at java.util.HashMap$ValueIterator.next(HashMap.java:1466)
- at dp.Cme.run(Cme.java:19)
- at java.lang.Thread.run(Thread.java:745)
看看原因:
- final Node < K,
- V > nextNode() {
- Node < K,
- V > [] t;
- Node < K,
- V > e = next;
- if (modCount != expectedModCount) throw new ConcurrentModificationException();
- if (e == null) throw new NoSuchElementException();
- if ((next = (current = e).next) == null && (t = table) != null) {
- do {} while ( index < t . length && ( next = t [ index ++]) == null);
- }
- return e;
- }
还是期望值与传进来的元素数量不相等所导致的
解决办法:
由于 map 是 key-value 形式的, 并且 map 是有自己的空间的, 所以在 put 的时候, 并不影响
在迭代时是如何保证遍历出原来的 map 的 value 的, 具体原因我会再深入, 及时修改文章
6.hashtable 怎么就不能代替在多线程情况下的 hashMap?
Hashtable 和 ConcurrentHashMap 都可以用于多线程的环境, 但是当 Hashtable 的大小增加到一定的时候, 性能会急剧下降, 因为迭代时需要被锁定很长的时间因为 ConcurrentHashMap 引入了分割(segmentation), 不论它变得多么大, 仅仅需要锁定 map 的某个部分, 而其它的线程不需要等到迭代完成才能访问 map 简而言之, 在迭代的过程中, ConcurrentHashMap 仅仅锁定 map 的某个部分, 而 Hashtable 则会锁定整个 map 也就是说代替是可以代替, 但是在解决线程安全的同时降低了性能, 所以选用 ConcurrentHashMap
7. 为什么要在多线程下使用 hashMap 呢?
这是废话线程不安全你还偏要用, 非得跟自己过不去吗?
参考资料:
CocurrentHashMap 和 Hashtable 的区别
Iterator
JCIP_5_01_CopyOnWriteArrayList 为什么不会产生 ConcurrentModificationException
老生常谈, HashMap 的死循环
理解和解决 Java 并发修改异常 ConcurrentModificationException
来源: https://www.cnblogs.com/huhu1203/p/8445827.html