先来看一个例子:

     @Test
void test2() {
ArrayList<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
// list.add("f");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String str = (String) iterator.next();
if(str.equals("d")){
list.remove(str);
// iterator.remove();
}else{
System.out.println(str);
}
}
}

Console:

 a
b
c

这段代码运行不会报错,但输出不符合我们的预期。我们预期应该是输出:a,b,c,e

如果我们换成删除c,则会报 java.util.ConcurrentModificationException 异常

为什么会出现这个问题呢?

我们跟踪到源码可以发现,ArrayList 类有一个成员变量 modCount 继承自 AbstractList 类

AbstractList 类:

 protected transient int modCount = 0;
transient 关键字修饰,表明变量不必参与序列化。
这个变量的作用是记录list 结构被修改的次数
在add 和 remove 方法里面都能看到它的身影
add方法:
     //add方法会调用此方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

remove(int)方法:

     public E remove(int index) {
rangeCheck(index); modCount++;
E oldValue = elementData(index); int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work return oldValue;
}

remove(Object) 方法:

     public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

而ArrayList的迭代器是ArrayList的一个内部类:

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

Itr 源码:

     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();
}
} @Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

可以看到迭代器有一个内部变量 expectedModCount  是拷贝的 modCount

next() 会检查modCount 是否改变,若改变则抛出 ConcurrentModificationException 异常

所以文章开头的第一种情况就可以解释了,之所以删除d’没有报错是因为,d是倒数第二个元素,删除之后,游标 cursor == size

在hasNext方法执行的时候就已经返回了false,没有执行next()方法

而改成c以后,next()会被执行,从而检查出modCount 被修改,抛出异常。

而调用迭代器的remove 方法就不会出现这种情况,因为它更新了expectedModCount,使之与 modCount 保持一致

迭代器的remove方法:

 public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
            //更新expectedModCount
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

快速失败是为了保证数据的安全性,防止并发修改出现的错误。HashMap 等等非线程安全的集合也有同样的机制。

并发情况下应该使用线程安全的集合工具,例如CopyOnWriteArrayList,ConcurrentHashMap 等

java ArrayList 迭代器快速失败源码分析的更多相关文章

  1. java多线程系列(九)---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

  2. Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式

    在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...

  3. Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式

    通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...

  4. Java并发系列[5]----ReentrantLock源码分析

    在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...

  5. java集合系列之LinkedList源码分析

    java集合系列之LinkedList源码分析 LinkedList数据结构简介 LinkedList底层是通过双端双向链表实现的,其基本数据结构如下,每一个节点类为Node对象,每个Node节点包含 ...

  6. Java集合系列[4]----LinkedHashMap源码分析

    这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...

  7. JAVA设计模式-动态代理(Proxy)源码分析

    在文章:JAVA设计模式-动态代理(Proxy)示例及说明中,为动态代理设计模式举了一个小小的例子,那么这篇文章就来分析一下源码的实现. 一,Proxy.newProxyInstance方法 @Cal ...

  8. ArrayList实现原理及源码分析之JDK8

    转载 ArrayList源码分析 一.ArrayList介绍 Java 集合框架主要包括两种类型的容器: 一种是集合(Collection),存储一个元素集合. 一种是图(Map),存储键/值对映射. ...

  9. Java集合系列:-----------03ArrayList源码分析

    上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayLi ...

随机推荐

  1. Pandas——ix 与 loc 与 iloc 与 icol 的区别

    来自:https://blog.csdn.net/xw_classmate/article/details/51333646 来自:https://blog.csdn.net/chenKFKevin/ ...

  2. The folder is already a source folder

    不知为啥,创建了一个maven项目后,发现只有src/main/resources这个资源文件夹,然后,右键新建 Source Folder 时提示 “The folder is already a ...

  3. 大话WebRTC的前世今生

    音视频的历史 音视频可以说是人类与生俱来的需求,人一出生就要用耳朵听,用眼睛看.中国的古代神话中为此还专门设置了两位神仙(千里眼和顺风耳),他们可以听到或看到千里之外的声音或景像. 为了解决听的远和看 ...

  4. 力扣(LeetCode) 771. 宝石与石头

    给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头. S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石. J 中的字母不重复,J 和 S中的所有字符都是字母 ...

  5. repeater绑定dropdownlist,jquery+ajax页面无刷新,修改dropdownlist默认值

    html代码: <td>                        <asp:HiddenField ID="hiddenchuli" Value='< ...

  6. mui 引导页

    引导页制作 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <met ...

  7. ajax和iframe区别

    ajax和iframe https://segmentfault.com/a/1190000011967786 ajax和iframe的区别 1.都是局部刷新 2.iframe是同步的,而ajax是异 ...

  8. 日常英语---二、注册google的api的key

    日常英语---二.注册google的api的key 一.总结 一句话总结:register google api key register_google_api_key 1.请通过电子邮件向我发送有关 ...

  9. Python学习笔记之参数解析

    python提供了两种方法进行命令行的参数解析,分别是getopt和optparse类中的模块OptionParser,下面分别详细了解这两个模块: 1.getopt模块 首先复习C语言的命令行解析: ...

  10. java中的Sort函数,你值得看

    基于C语言中的sort如此这么方便,自然而然,java中也有类似C的sort函数. 1.普通数组:Arrays.sort(数组名,开始位置,结束位置). 2.类中属性排序: 模板: class A { ...