List如何在遍历时删除元素?(fail-fast机制)

有两个线程(线程A,线程B),其中线程A负责遍历list,线程B修改list结构。线程A在遍历list过程中(此时,exceptedModCount=modCount),线程B启动并增加或删除一个元素,这时modCount值发生改变,modCount+1。线程A继续遍历执行next()方法的时候做checkForComodification()发现exceptedModCount != modCount,从而抛出ConcurrentModificationException异常,从而产生fail-fast机制。

在父类AbstractList中定义了一个int类型的属性modCount=0:

在ArrayList的所有涉及结构变化的方法中都会改变modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()。这些方法每调用一次,modCount的值就加1。

例子:List如何在遍历时删除元素?

1、错误方式:

(1) 通过下标遍历进行删除:

List如何在遍历时删除元素?(fail-fast机制)_第1张图片

因为下标是固定死的自增,但list的长度随着删除元素而减小,并且后面的元素往前移了1位,所以后面的元素会遍历不到。在i=2时,由于删了1个元素,size=4,所以循环直接结束。因此这种方式是不符合预期的。

(2) 通过迭代器的方式遍历list并list.remove()删除元素:报异常ConcurrentModificationException错误。

特殊现象:当只删除一个元素的时候,如果删除倒数第二个元素却不报错。为什么?

List如何在遍历时删除元素?(fail-fast机制)_第2张图片

 

List如何在遍历时删除元素?(fail-fast机制)_第3张图片

解析:ArrayList使用size属性来维护自已的状态,而Iterator使用cursor来维护自已的状态。关键在于当cursor==size时(迭代器认为已经遍历完list),退出while循环(当只删除一个元素的时候,如果删除倒数第二个元素则不报错的原因)。此外,集合迭代中都存在字段modCount记录list结构上被修改的次数(快速失败机制)。

1、list=[0,1,2,3,4],it.hasNext(),cursor=0,size=3。cursor!=size。返回true,进入while循环

2、Integer integer = it.next();时,迭代器通过get(cursor),然后cursor++实现遍历,Iterator会做checkForComodification()检查,此时modCount=expectedModCount,因此没有抛出异常。

List如何在遍历时删除元素?(fail-fast机制)_第4张图片

 

3、it.hasNext(),cursor=1,size=3。cursor!=size,返回true,进入while。

4、Integer integer = it.next();时,cursor++,Iterator会做checkForComodification()检查,此时modCount=expectedModCount,因此没有抛出异常。

......

5、删除的不是倒数第二个元素的情况时:当if(integer==2) {list.remove(integer); },list结构改变,Iterator会做checkForComodification()检查,modCount+1,modCount!=expectedModCount,抛出异常。

List如何在遍历时删除元素?(fail-fast机制)_第5张图片

6、删除的是倒数第二个元素的情况时:cursor==size,退出while循环,不会执行checkForComodification()方法,因此不报错。

7、验证:

List如何在遍历时删除元素?(fail-fast机制)_第6张图片

 

2、正确方式:

(1) 倒序for遍历删除:

List如何在遍历时删除元素?(fail-fast机制)_第7张图片

这样可确保每个元素都遍历到。

(2) 使用迭代器的方式iterator.remove删除元素:

List如何在遍历时删除元素?(fail-fast机制)_第8张图片

你可能感兴趣的:(JAVA)