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、获取互斥锁
*2、Copy当前元素数组创建新数组,数组内存空间增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

原创不易,转载请注明地址http://www.cnblogs.com/huhu1203/p/8445827.html ,如有问题,请指正!

在ConcurrentModificationException异常上的联想的更多相关文章

  1. Java ConcurrentModificationException异常原因和解决方法

    Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...

  2. Java并发编程:Java ConcurrentModificationException异常原因和解决方法

    Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...

  3. java集合--java.util.ConcurrentModificationException异常

    ConcurrentModificationException 异常:并发修改异常,当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常.一个线程对collection集合迭代,另一个线程对Co ...

  4. 【转】Java ConcurrentModificationException 异常分析与解决方案--还不错

    原文网址:http://www.2cto.com/kf/201403/286536.html 一.单线程 1. 异常情况举例 只要抛出出现异常,可以肯定的是代码一定有错误的地方.先来看看都有哪些情况会 ...

  5. 【转】ConcurrentModificationException异常解决办法 --不错

    原文网址:http://blog.sina.com.cn/s/blog_465bcfba01000ds7.html 1月30日java.util.ConcurrentModificationExcep ...

  6. 【转】Java ConcurrentModificationException异常原因和解决方法

    原文网址:http://www.cnblogs.com/dolphin0520/p/3933551.html Java ConcurrentModificationException异常原因和解决方法 ...

  7. (转)Java ConcurrentModificationException异常原因和解决方法

    转载自:http://www.cnblogs.com/dolphin0520/p/3933551.html 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会 ...

  8. java.util.ConcurrentModificationException 异常问题详解

    环境:JDK 1.8.0_111 在Java开发过程中,使用iterator遍历集合的同时对集合进行修改就会出现java.util.ConcurrentModificationException异常, ...

  9. java.util.ConcurrentModificationException异常原因及解决方法

    在java语言中,ArrayList是一个很常用的类,在编程中经常要对ArrayList进行删除操作,在使用remove方法对ArrayList进行删除操作时,报java.util.Concurren ...

随机推荐

  1. [国嵌攻略][173][BOA嵌入式服务器移植]

    1.解压boa嵌入式web服务 tar zxvf boa-0.94.13.tar.gz 2.进入src目录生成配置文件 ./configure 3.修改生成的Makefile CC=arm-linux ...

  2. Redis进阶实践之六Redis Desktop Manager连接Windows和Linux系统上的Redis服务

    一.引言 今天本来没有打算写这篇文章,当初我感觉使用这个工具应该很简单,下载的过程也不复杂,也没有打算记录下来.但是在使用的过程中还是出现了一些问题,为了给第一次使用Redis Desktop Man ...

  3. redis常见命令使用

    这篇经验主要介绍了Redis常见用的一些操作命令.这篇例子是在windows上操作的.linux类似.写的一些基础,大神就别看了. 工具/原料   redis windows 方法/步骤   1 可以 ...

  4. 织梦dedeCMS留言薄

    dedeCMS留言薄模塊名爲guestbook, 留言薄模板:/templets/plus/guestbook.htm; 留言回覆模板: 管理員回覆調用/templets/plus/guestbook ...

  5. [拾 得] zip gzip bzip2 & tar 压缩/打包 四大金刚

    坚持知识分享,该文章由Alopex编著, 转载请注明源地址: http://www.cnblogs.com/alopex/    索引: 介绍压缩和打包 gzip bzip2 zip 的基本使用 gz ...

  6. 使用 EclEmma 进行覆盖测试

    开源软件测试工具 EclEmma,它能够对由 Java 语言编写的程序进行覆盖测试,从而对程序运行的结果生成详尽的覆盖测试报告. UT-Junit 安装 EclEmma 插件 安装 EclEmma 插 ...

  7. goDaddy SSL证书 Nginx配置全流程 (转)

    好长时间没动过这玩意了,今天突然用到,忘的一干二净.在此做个笔记吧! 一.购买Godaddy SSL证书 1.打开Godaddy官网 http://www.godaddy.com/: 2.点击网站导航 ...

  8. myeclipse10不用打开myeclipse configuration center安装插件的方法

    我使用myeclipse10,网上找了一大堆的插件安装方法,全部都是要通过help->myeclipse configuration center进行安装 不用打开myeclipse  conf ...

  9. Java Servlet API中文说明文档

    Java Servlet API中文说明文档 目 录 1.... Servet资料 1.1      绪言 1.2      谁需要读这份文档 1.3      Java Servlet API的组成 ...

  10. ThinkPhp关闭Debug后出错解决方案

    注:我使用的是ThinkPHP的3.2版本,其他版本类似 从自己入手PHP开发以来,一直使用的是ThinkPHP的框架,前几天偶然间碰到了一个错误,在Debug模式下网站一切正常,而关闭Debug进行 ...