Java异常:Java.util.ConcurrentModificationException异常处理-程序员宅基地

技术标签: java  Iterator  

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

Java异常:Java.util.ConcurrentModificationException异常处理


前言

在开发中使用了迭代器对 List 进行遍历,遇到了java.util.ConcurrentModificationException报错,本文记录了这个错误出现的原因和如何解决


一、迭代器Iterator是什么?

迭代器是属于设计模式之一,迭代器模式提供了一种方法来顺序访问一个聚合对象中各个元素,而不保留该对象的内部表示。

1)Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
2)所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。
3)Iterator仅用于遍历集合,Iterator本身并不存放对象。

常见使用形式为:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TestDemo {
    
    public static void main(String[] args) {
    
        List<Integer> list = new ArrayList<>();
        list.add(111);
        list.add(222);
        list.add(333);

        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()) {
    
            int value = iterator.next();
            System.out.print(value + " ");
        }
    }
}

二、Java.util.ConcurrentModificationException原因

一个典型的代码实例:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@SuppressWarnings({
    "all"})
public class TestDemo {
    
    public static void main(String[] args) {
    
        List<Integer> list = new ArrayList<>();
        list.add(111);
        list.add(222);
        list.add(333);

        System.out.println("删除前:" + list);
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
    
            Integer value =  iterator.next();
            if(value == 111) list.remove(Integer.valueOf(111));
        }
        System.out.println("删除后" + list);
    }
}

问题就出现在:应该调用 Iterator.remove() 而不是 list.remove()!
在这里插入图片描述
运行结果中抛出java.util.ConcurrentModificationException异常信息。这是因为触发了集合中并发修改的异常 接下来我们通过源码对抛出异常的原因进行剖析。

    public Iterator<E> iterator() {
    
        return new Itr();
    }

在ArrayList集合的Iterator方法中,是通过返回Itr对象来获得迭代器的。Itr是ArrayList的一个内部类,它实现了Iterator接口,代码如下:

   private class Itr implements Iterator<E> {
    
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {
    }

        public boolean hasNext() {
    
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        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];
        }

        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();
            }
        }

Itr类属性

属性 含义
cursor 索引下标,表示下一个可以访问的元素的索引,默认值为 0
lastRet 索引下标,表示上一个元素的索引,默认值为 -1
expectedModCount 对集合修改的版本号,初始值为ModCount

ModCount定义在AbstractList接口中,初始值为0,定义如下:

 protected transient int modCount = 0;

ModCount是版本号,在对集合进行变更操作(增加、删除、修改等)的时候会对版本号进行 +1 操作。

结合上述代码进行抛出java.util.ConcurrentModificationException异常的解释。

①初始化ArrayList,添加三次元素,即三次调用add()方法,进行三次modCount++; 此时,m o d C o u n t = 3 , s i z e = 3 ; \color{
    red}{
    modCount = 3,size = 3}modCount=3,size=3;
②初始化Iterator迭代器进行循环,此时,e x p e c t e d M o d C o u n t = m o d C o u n t = 3 , \color{
    red}{
    expectedModCount = modCount=3}expectedModCount=modCount=3, c u r s o r = 0 , l a s t R e t =1 \color{
    red}{
    cursor=0,lastRet = -1}cursor=0,lastRet=1
③进行hasNext判断,cursor != size;成立,进入循环
④调用next()方法,首先进行checkForComodification()校验,m o d C o u n t = = e x p e c t e d M o d C o u n t \color{
    red}{
    modCount == expectedModCount}modCount==expectedModCount,校验通过,返回值,此时l a s t R e t = 0 ; c u r s o r = 1 \color{
    red}{
    lastRet = 0;cursor = 1}lastRet=0;cursor=1

 final void checkForComodification() {
    
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
  }
  
⑤调用集合remove()方法,modCount++;,此时}modCount=4;size=2
⑥再次调用hasNext()方法判断,cursor != size;成立,进入循环
⑦调用next()方法进行校验,modCount ! = expectedModCount,校验未通过,抛出java.util.ConcurrentModificationException异常

总结:

①在使用迭代器的remove()操作时,会将更新后的modCount给expectedModCount,两者会得到同步,但是在调用集合的remove()方法后,两个不会进行同步,进而导致在checkForComodification()校验时不通过,抛出java.util.ConcurrentModificationException异常。
②所以,在单线程下使用迭代器是没有问题的,但是在多线程下同时操作集合就不允许了,可以通过fail-fast快速失败机制,快速判断是否存在同时操作问题。因此,集合在多线程下使用是不安全的。

特别说明!
本文主要参考自: Java迭代器详解,看这一篇就够了

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_46119575/article/details/131580050

智能推荐

Pandas对行/列求和_pandas对具体列求和-程序员宅基地

文章浏览阅读5.3k次,点赞3次,收藏18次。Pandas对行/列求和_pandas对具体列求和

Visio--用例图、类图、顺序图、活动图_visio画用例图-程序员宅基地

文章浏览阅读1.9w次,点赞32次,收藏228次。做个小结。_visio画用例图

程序员的路----程序员一定要仔细看_程序员的道路-程序员宅基地

文章浏览阅读2.2k次。在网上看到的恶搞程序员的图片,实在是搞笑。看一次笑一次!程序猿的十年他不是乞丐,请尊称他为程序猿。对,他就是程序猿!其实,你们看到的不是僵尸,他们都是苦逼的程序猿!这不是恶搞的,是一个真实的故事!看他的拐杖,其实也挺可怜的!不知是不是敲代码敲多了,把脚敲颓了!_程序员的道路

从Atlas到Microsoft ASP.NET AJAX(6) - Networking, Application Services-程序员宅基地

文章浏览阅读42次。NetworkingCalling Web Service Methods from Script  为了简化Web Services方法调用,客户端代理的设计被改变了,它在方法调用和回调函数设置方面提供了强大的灵活性。  下面的例子展示了CTP版本中Web Services方法的客户端调用,以及回调函数的使用方式。第一个例子展示了在CTP版本中Web ..._.net6 applicationservices

一文带你了解socket网络编程以及详解过程和原理_socket编程-程序员宅基地

文章浏览阅读1.1w次,点赞42次,收藏178次。Socket(套接字)是计算机网络编程中的一种抽象概念,它提供了在网络上进行通信的接口。通过使用 Socket,可以在不同计算机之间建立连接,并进行数据的传输和交换。通过 Socket,客户端可以与服务器建立连接并发送请求,服务器接收请求并返回响应。通过 Socket,可以在多个用户之间实现实时的文字、音频或视频通信。可以使用 Socket 在不同计算机之间传输文件,如上传和下载文件。可通过 Socket 在远程计算机上执行指令或操作。_socket编程

nodejs第三方模块 express框架 传参_nodejs express 模块间参数传递-程序员宅基地

文章浏览阅读413次。nodejs第三方模块 express框架 传参get接口动态参数以这个举例 http://localhost:8001/edit/124其中,/124传递的id值124就是动态参数服务器通过req.params获取app.get("/edit/:id",(req,res)=>{ console.log(req.params) res.send("发送成功")})这个在我们删除和编辑数据的时候最常用,根据指定的id删除内容,以及编辑内容时的数据回显。查询字符串参数_nodejs express 模块间参数传递

随便推点

计算机考研408每日一题 day78_命中率高且电路实现简单的cache内存映射方式-程序员宅基地

文章浏览阅读392次。对于二叉排序树,下面的说法 ___是正确的。(华南理工大学 2006年)设备驱动程序在读写磁盘数据时一般釆用下列哪种I/O方式?(中国科学院大学 2018)下列___交换技术是独占信道工作方式。(中南大学 2006年)命中率高且电路实现简单的Cache 与内存映射方式是___映射方式。(中国科学院大学 2015)_命中率高且电路实现简单的cache内存映射方式

Unhandled exceptions: java.lang.IllegalAccessException, java.lang.reflect.InvocationTargetException_unhandled exceptions: java.lang.instantiationexcep-程序员宅基地

文章浏览阅读3.2k次。可能是没有异常处理,只需要加上throws Exception就解决问题了。_unhandled exceptions: java.lang.instantiationexception, java.lang.illegalacc

webview ERROR_UNSUPPORTED_SCHEME ,errorcode=-10问题处理-程序员宅基地

文章浏览阅读1.5k次。webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { try{ if(url.startsWith("baidumap://")){ Intent _error_unsupported_scheme

embed标签-程序员宅基地

文章浏览阅读66次。Embed  (一)、基本语法:  embed src=url  说明:embed可以用来插入各种多媒体,格式可以是 Midi、Wav、AIFF、AU、MP3等等, Netscape及新版的IE 都支持。url为音频或视频文件及其路径,可以是相对路径或绝对路径。  示例:<embed src="your.mid">  (二)、属性设置:  1、自动播放...

php 上传文件漏洞,PHP -- 文件包含、文件上传漏洞-程序员宅基地

文章浏览阅读330次。PHP -- 文件包含、文件上传漏洞PHP -- 文件包含、文件上传漏洞文件包含文件引入漏洞,是由一个动态页面编译时引入另一个文件的操作。文件引入本身是没有问题,它是用于统一页面风格,减少代码冗余的一种技术。但是在特定的场景下就会形成漏洞jsp:include指令和include动作,其中include动作用于引入动态文件php:include(),include_once(),require()..._php文件上传4漏洞

配置NGINX同时运行 https 和 http_nginx 和 http无法同时启动-程序员宅基地

文章浏览阅读406次。SSL 是需要申请证书的,key和PEM文件要放到服务器路径。然后NGINX下要进行443端口和80端口的绑定。server { listen 80; server_name ietaiji.com www.ietaiji.com; root "D:/aaa/WWW/ietaiji"; index index.html_nginx 和 http无法同时启动