在ConcurrentModificationException异常上的联想
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呢?
这是废话。线程不安全你还偏要用,非得跟自己过不去吗?
参考资料:
JCIP_5_01_CopyOnWriteArrayList为什么不会产生ConcurrentModificationException
理解和解决Java并发修改异常ConcurrentModificationException
原创不易,转载请注明地址http://www.cnblogs.com/huhu1203/p/8445827.html ,如有问题,请指正!
在ConcurrentModificationException异常上的联想的更多相关文章
- Java ConcurrentModificationException异常原因和解决方法
Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...
- Java并发编程:Java ConcurrentModificationException异常原因和解决方法
Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...
- java集合--java.util.ConcurrentModificationException异常
ConcurrentModificationException 异常:并发修改异常,当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常.一个线程对collection集合迭代,另一个线程对Co ...
- 【转】Java ConcurrentModificationException 异常分析与解决方案--还不错
原文网址:http://www.2cto.com/kf/201403/286536.html 一.单线程 1. 异常情况举例 只要抛出出现异常,可以肯定的是代码一定有错误的地方.先来看看都有哪些情况会 ...
- 【转】ConcurrentModificationException异常解决办法 --不错
原文网址:http://blog.sina.com.cn/s/blog_465bcfba01000ds7.html 1月30日java.util.ConcurrentModificationExcep ...
- 【转】Java ConcurrentModificationException异常原因和解决方法
原文网址:http://www.cnblogs.com/dolphin0520/p/3933551.html Java ConcurrentModificationException异常原因和解决方法 ...
- (转)Java ConcurrentModificationException异常原因和解决方法
转载自:http://www.cnblogs.com/dolphin0520/p/3933551.html 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会 ...
- java.util.ConcurrentModificationException 异常问题详解
环境:JDK 1.8.0_111 在Java开发过程中,使用iterator遍历集合的同时对集合进行修改就会出现java.util.ConcurrentModificationException异常, ...
- java.util.ConcurrentModificationException异常原因及解决方法
在java语言中,ArrayList是一个很常用的类,在编程中经常要对ArrayList进行删除操作,在使用remove方法对ArrayList进行删除操作时,报java.util.Concurren ...
随机推荐
- 【Zigbee技术入门教程-号外】基于Z-Stack协议栈的抢答系统
[Zigbee技术入门教程-号外]基于Z-Stack协议栈的抢答系统 广东职业技术学院 欧浩源 一.引言 2017年全国职业院校技能大赛"物联网技术应用"赛项中任务三题2的 ...
- [国嵌攻略][098][Linux内核简介]
Linux系统架构 1.用户空间:应用程序.C函数库 2.内核空间:系统调用接口.内核.体系结构相关代码 Linux系统利用处理器不同的工作模式,使用其中的两个级别分别来运行Linux内核与应用程序, ...
- Caused by: java.sql.SQLException: Couldn't perform the operation getAutoCommit: You can't perform any operations on this connection. It has been automatically closed by Proxool for some reason (see lo
系统启动,一段时间不操作,然后在来操作时,报错如下: Caused by: java.sql.SQLException: Couldn't perform the operation getAutoC ...
- Spring MVC集成Swagger
什么是Swagger? 大部分 Web 应用程序都支持 RESTful API,但不同于 SOAP API——REST API 依赖于 HTTP 方法,缺少与 Web 服务描述语言(Web Servi ...
- 十二个 ASP.NET Core 例子——过滤器
目录: 过滤器介绍 过滤器类别 自定义过滤器和过滤特性 直接短路返回内容 过滤器与中间件的区别 如果要全局日志,不要用过滤器 官方文档传送门 1.过滤器介绍 没有权限直接返回,资源缓存,Action执 ...
- ceil与intval区别
float ceil(float value)ceil返回不小于value的最小整数,返回值仍是float型 int intval ( mixed value [, int base]) int ...
- 版本控制——TortoiseSVN (4)多版本并行开发 B
=================================版权声明================================= 版权声明:原创文章 禁止转载 请通过右侧公告中的“联系邮 ...
- python 操作python
#!/usr/bin/env python#_*_ coding:utf-8 _*_ import MySQLdb # 打开门conn = MySQLdb.connect(host='192.168. ...
- python下划线作用初识
单下划线(例:_textchar) 以单下划线做前缀的名称指定了这个名称是"私有的".在 有些 导入import * 的场景中,下一个使用你代码的人(或者你本人)会明白这个名称仅内 ...
- jsp页面遍历List<Array>
不难的遍历,难住了“前辈”,因此决定分享一下希望帮助那些还迷糊的人. 数据结构下如图所示,之前的前辈遍历方法如下,厉害哦!当然,代码直接抛异常哈, <c:if test="${!emp ...