当前位置: 首页 > 科技观察

该死!简单的删除集合中的元素居然报错

时间:2023-03-22 00:48:19 科技观察

前言什么是快速失败:fail-fast机制是java集合(Collection)中的一种错误机制。它只能用于检测错误,因为JDK不保证fail-fast机制一定会发生。当多个线程对同一集合的内容进行操作时,可能会生成fail-fast事件。运行如下代码,会出现异常://Thinkingaboutfail-fastpublicclassFailFastTest{publicstaticvoidmain(String[]args){//ConstructArrayListListlist=newArrayList<>();list.add(1);list.add(2);list.add(3);list.add(4);for(inti:list){list.remove(1);}}}控制台会输出如下异常:Whyshouldthiserror被举报??途中错误的地方是ArrayList中的代码,找到代码在这里:finalvoidcheckForComodification(){if(modCount!=expectedModCount)thrownewConcurrentModificationException();}modCount是这个集合被修改的次数,这个属性来自AbstractList,而我们的ArrayList就是继承自抽象类。protectedtransientintmodCount=0;expectedModCount是什么?我们在遍历的时候调试,发现在执行forEach循环的时候,实际上使用了下面这个方法迭代器,hasNext方法publicIteratoriterator(){returnnewItr();}判断是否有下一个元素publicbooleanhasNext(){returncursor!=size;}next()方法用于获取元素publicEnext(){checkForComodification();//注意这个方法inti=cursor;if(i>=size)thrownewNoSuchElementException();Object[]elementData=ArrayList.this.elementData;if(i>=elementData.length)thrownewConcurrentModificationException();cursor=i+1;return(E)elementData[lastRet=i];}点击这个newItr(),惊讶的发现expectedModCount这里赋值和modCount相同privateclassItrimplementsIterator{intcursor;//indexofnextelementtoreturnintlastRet=-1;//indexoflastelementreturned;-1ifnosuchintexpectedModCount=modCount;//注意:这里赋值.........接下来看在ArrayList的remove()方法中,增加了modCount,这是e的原因错误publicEremove(intindex){rangeCheck(index);modCount++;//++对modCount的操作EoldValue=elementData(index);intnumMoved=size-index-1;if(numMoved>0)System.arraycopy(elementData,index+1,elementData,index,numMoved);elementData[--size]=null;//cleartoletGCdoitsworkreturnoldValue;}上面的next()方法调用了一个checkForComodification()方法,下面贴上这个方法的代码finalvoidcheckForComodification(){if(modCount!=expectedModCount)thrownewConcurrentModificationException();}ArrayList中的remove()方法进行modCount++操作。原来是我们操作集合后修改了modCount,导致上面的代码成立,从而抛出异常。但是当我们使用Itr类的remove时,也就是下面的代码改变元素时,不会抛出ConcurrentModificationExceptionpublicvoidremove(){if(lastRet<0)thrownewIllegalStateException();checkForComodification();try{ArrayList.this.remove(lastRet);cursor=lastRet;lastRet=-1;//将ArrayList的modCount赋值给Itr类的expectedModCount//这样再次调用next方法的时候就没有了两个值不一致,以免报错(lastRet);之后明明是modCount++,却立马让expectedModCount=modCount。不会抛出异常梳理一下整个过程:1、for循环遍历本质上是调用Itr类的方法进行遍历(Itr类实现了Iterator)。ArrayList继承AbstractList)赋值给Itr类的expectedModCount3。当for循环中调用的remove()方法为ArrayList时,该方法会对modCount进行++操作。4.remove方法调用后,遍历会调用Itr方法的next(),这个next()方法中的checkForComodification()方法会比较modCount和expectedModCount。由于remove方法已经操作了modCount,这两个值不会相等,所以报错。如何提高?1、可以使用Itr中的remove方法进行改进。改进后的代码如下publicstaticvoidmain(String[]args){//BuildArrayListListlist=newArrayList<>();list.add(1);list。add(2);list.add(3);list.add(4);Iteratoriterator=list.iterator();while(iterator.hasNext()){iterator.next();iterator.remove();}System.out.println(list.size());//0}2.使用CopyOnWriterArrayList代替Arraylist,当它对ArrayList进行操作时,会先复制一份数据,然后再更新回来替换它Old,所以CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。这是CopyOnWriterArrayList的故障安全机制。当集合的结构发生变化时,故障安全机制会从原来的集合中复制一份数据,然后遍历复制的数据。故障安全机制在JUC包的集合中是用这种机制实现的。fail-safe虽然不会抛出异常,但是它有以下缺点:1.复制需要额外的空间和时间开销。2.不能保证遍历的是最新内容。综上所述,对于fail-fast机制,当我们要对List集合进行操作时,可以在遍历过程中使用Iterator的remove()方法删除元素,或者使用fail-safe机制的CopyOnWriterArrayList。当然,我们在使用的时候需要权衡利弊,结合相关的业务场景。