List中subList方法抛出异常java.util.ConcurrentModificationException原理分析_list什么情况会抛出异常_养家糊口的猫的博客-程序员秘密

技术标签: Java  

1、首先从测试代码开始:

public class Test {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0;i<6000;i++){
            list.add(i);
        }
        List<Integer> list1 = list.subList(0,3000);
        List<Integer> list2 = list.subList(3000,6000);
        list2.clear();
        System.out.println("list1 = " + list1);

    }
}

首先初始化一个6000个元素的list,然后,利用list.subList()截取3000个元素到list1中,再取出后3000个元素到list2中,然后清空list2,最后再打印list1,此时将抛出异常:

2、前戏知识:

subList()方法原理分析:

上面的测试方式为什么会出现这个情况,看上去明明没有任何问题,但是打印list1的时候就抛出异常,肯定不可能是System.out.println()有bug吧,再来仔细看看代码,似乎只有打印语句前面几句话会出现问题,那么就是subList()的调用以及clear()这几句代码了,那么问题到底出现在哪里,我们来一探究竟;

接下来我们首先看一下ArrayList中对subList()方法的实现的源码,看它究竟干了些什么事儿:

在subList()方法的源码中首先调用了 subListRangeCheck(fromIndex, toIndex, size) 这个方法主要作用就是判断subList()传入的参数是否合规,这里不是重点,重点在于它  return new SubList(this, 0, fromIndex, toIndex),返回了一个SubList对象,继续往下看一下这个SubList对象,源码在1010行:

通过源码可以看到,这个SubList对象是一个内部类,

2.1、在构造对象时,会传入4个参数:

AbstractList<E> parent:当前调用subList()方法的list对象

int offset:偏移量(从0开始)

int fromIndex:开始下标(包含)

int toIndex:结束下标(不包含)

2.2、在构造器内部:

将传入的parent赋给SubList对象的成员变量parent;

fromIndex赋给SubList对象的成员变量parentOffset;

offset+fromIndex赋给SubList对象的成员变量offset,用于记录元素的偏移量;

toIndex - fromIndex赋给SubList对象的成员变量size,用于记录此时会返回的数据量大小;

最后一个是 ArrayList.this.modCount 赋给SubList对象的成员变量modCount ,这个赋值比较关键,记录了修改过的次数,默认为0;

到这里,构造一个SubList对象就完成了,你可能会有疑问,只是单纯的构造了一个SubList对象,那么是怎么进行赋值取值的;解决这个问题,来看一下SubList对象的get()方法:

在get()方法中,最终返回的是 ArrayList.this.elementData(offset + index);可以看到,它是从当前的ArrayList对象中维护的一个elementData()方法中取值,再来看elementData()这个方法:

返回的是elementData这个数组中的元素:

由此可见:SubList对象中操作的集合与原始list中操作的集合是同一个集合,通过offset偏移量加上index来标记元素的位置;所以,当你操作原始list或者截取元素后生成的list1集合,都是影响同一个集合。

3、高潮部分:

异常产生分析:

有了上面第二步的分析,有了一个基本认识,那就是list.subList()方法返回的集合会直接影响原始的list集合,接下来继续分析java.util.ConcurrentModificationException异常出现的原因;

再次回到测试代码的以下四句代码:

List<Integer> list1 = list.subList(0,3000);
List<Integer> list2 = list.subList(3000,6000);
list2.clear();
System.out.println("list1 = " + list1);

首先通过  List<Integer> list1 = list.subList(0,3000); 等到一个list1; 

然后再次通过  List<Integer> list2= list.subList(3000,6000); 等到一个list2; 

然后清空list2 即list2.clear();

最后打印:System.out.println("list1 = " + list1);

由于上面分析我们知道,list2调用clear()方法,那么此时原始list维护的底层elementData数组势必会受影响,具体就是会把这后面3000个元素给删除掉,此时list1再去打印,它会调用自己重写的迭代方法iterator()进行遍历,然后调用父级AbstractList的listIterator()方法,由于SubList类继承了AbstractList 所以它会来调用SubList类的listIterator(final int index)方法,此时该方法内部在第一句就调用了checkForComodification();这个方法:

 

接下来看 checkForComodification()这个方法在干什么:

重点来了,这个方法里面首先判断了 ArrayList.this.modCount 与 this.modCount(即SubList的modCount)是否相同,如果不相同则抛出异常java.util.ConcurrentModificationException,写得累死我了,绕了一大圈终于写到这个异常了,在生成list1时,它在实例化一个SubList对象时将原始list的modCount赋值给了SubList对象,此时是默认值0,当list2.clear()时,原始list的modCount已经发生了变化,即不再是0,所以 此时打印list1时,checkForComodification()方法中的ArrayList.this.modCount != this.modCount判断肯定时true,所以这就是异常抛出的原因。

4、附上一位研究了subList()方法上面的注释得出的结论的图供大家参考学习:

 

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

智能推荐

nginx反向代理二级目录 导致vue项目静态文件css js访问不到_zh7314的博客-程序员秘密

2022年5月30日19:06:00nginx vue项目配置 server { listen 9888; server_name 127.0.0.1; error_log /data/log/nginx/houtai-error.log; access_log /data/log/nginx/houtai-ac...

Java 开源中文分词器Ansj 学习教程_java 中文分词工具_Charles Timber的博客-程序员秘密

Java有11大开源中文分词器,分别是word分词器,Ansj分词器,Stanford分词器,FudanNLP分词器,Jieba分词器,Jcseg分词器,MMSeg4j分词器,IKAnalyzer分词器,Paoding分词器,smartcn分词器,HanLP分词器。不同的分词器有不同的用法,定义的接口也不一样,至于效果哪个好,那要结合自己的应用场景自己来判断。这里我就主要介绍Ansj中文分词器,它是一个开源的 Java 中文分词工具,基于中科院的 ictclas 中文分词算法,比其他常用的开源分词工具(

Android:通过滤镜实现点击图片变暗效果_无知的小羊的博客-程序员秘密

转自:http://www.cnblogs.com/linjzong/p/4206283.html实现点击图片(ImageView)变暗效果,有一个较简单的方法,就是讲目标图片设置为背景图片(setBackground),再创建一个selector.xml文件,里面放置一张普通状态时的透明图片,一张点击状态下的棕色半透明图片,将其设置为ImageView的源图片。这样在点击ImageView时,源图

TP5多图上传,Jq多图片上传加预览,提示信息框使用的layer_tp5方框提示信息代码咋写_伟大的python程序员的博客-程序员秘密

&amp;lt;?phppublic function savemoreimg(){ $request = $this-&amp;gt;request; if ($request-&amp;gt;isPost()) { $param = $request-&amp;gt;post('file/a'); if(!is_array($param)) { return ['st'=&amp;gt;1,'msg'=...

Django3 + Angular12_soapcmd的博客-程序员秘密

边学边记录,希望可以一周完工,并且应用到DRF中。可能需要安装的工具VSCODE(可以用Pycharm等替代)Python-Django-DRFAngular(先要安装Node.jsMongoDB(可以用各种数据库替代,初学者建议直接默认Django自带的Sqlite)Postman、虚拟环境(不算工具,但建议学习并使用)以上软件的安装配置此处不再详述,可以Google一下解决,如果有必要自己会单独写一下最新版的教程,有什么安装的问题也可以评论告知。创建Django项目...

IE7 关闭窗口不弹出对话框_iteye_1409的博客-程序员秘密

要实现从一个页面跳转到另一个页面去,把第一个页面自动关闭,IE6的解决方式: window.open(url, '_blank', 'top=0,left=0,width=1018px,resizable=yes,scrollbars=yes,toolbar=no,location=no,menubar=no,status=no');window.opener = null;w...

随便推点

fda计算机系统验证要求,美国FDA计算机系统验证.pptx_王超逸q的博客-程序员秘密

文档介绍:内容简介1.计算机系统验证定义及范围2.计算机系统分类3.计算机系统发展及验证生命周期4.验证实施过程5.系统验收及确认6.系统使用及维护7.系统引退8.验证分工与职责9.计算机系统验证举例(PLC系统验证)两个法规FDAFinalGuidanceforIndustryandFDAStaff,2002GAMPGuideforValidationofAutomatedSystemsVer....

玩转ES,一文教你掌握IK中文分词器_es ik分词器使用_斗者_2013的博客-程序员秘密

ES默认的分词器对中文分词并不友好,所以我们一般会安装中文分词插件,以便能更好的支持中文分词检索。而ES的中文分词器中,最流行的必然是IK分词器。

过滤器fiflter的XML的配置信息_zzsyzxzm的博客-程序员秘密

过滤器名称 所对应的java类文件,包名和类名要写全 初始化参数的名称 初始化参数的值 /*

C#垃圾回收学习总结_bcbobo21cn的博客-程序员秘密

浅谈C#垃圾回收http://www.cnblogs.com/cuiyiming/archive/2013/03/26/2981931.html  理解C#垃圾回收机制我们首先说一下CLR(公共语言运行时,Common Language Runtime)它和Java虚拟机一样是一个运行时环境,核心功能包括:内存管理、程序集加载、安全性、异步处理和线程同步。CTS(Commo

从网上搜索的格式化crs_stat -t输出的内容,挺不错,改动了一点内容_crs_stat 格式化_hffyxhjy的博客-程序员秘密

创建一个脚本:vi crs_statt,脚本内容如下,本脚本在10g rac上测试成功,下面的CRS_HOME变量根据您的环境来修改:CRS_HOME=/opt/ora10g/product/10.2.0/crs_1RSC_KEY=$1QSTAT=-u AWK=/usr/bin/awk$AWK \  'BEGIN {printf "%

栈的应用——平衡符号_hengjie2009的博客-程序员秘密

package cn.thj.data_structures;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.InputStreamReader;import java.util.Stack;public class Bal