先来看一个例子:

     @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. Qt532.QSettings_默认分隔符

    1.http://bbs.csdn.net/topics/391831509?page=1 2楼: QSettings读取配置文件的内容是以逗号作为分隔符,以分号作为结束符.遇到分号就结束了,后面的内 ...

  2. MySQL学习(十一)

    MySQL的函数 1 数学函数 2 字符串函数 3 日期和时间函数 4 条件判断函数 5 系统信息函数 6 加密解密函数 7 其他函数 2 字符串函数 length计算的是字节长度 char_leng ...

  3. VNC安装配置

    1,安装VNC yum install tigervnc-server -y2,设定密码: vncpasswd root 3,配置服务文件,下面是配置了6个窗口. cp /lib/systemd/sy ...

  4. Codeforces 801C - Voltage Keepsake

    C. Voltage Keepsake 题目链接:http://codeforces.com/problemset/problem/801/C time limit per test 2 second ...

  5. mui 引导页

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

  6. Angular 学习笔记 ( timezone + moment + material date-picker + date pipe + asp.net core )

    参考 : https://stackoverflow.com/questions/29979609/time-conversion-with-timezoneinfo-for-past-years h ...

  7. ssh服务及安全配置

    1.清空防火墙 关闭 setenforcesetenforce   2 getenforce 3 setenforce 0 4 iptables -F 5 systemctl stop firewal ...

  8. jenkins之从0到1利用Git和Ant插件打war包并自动部署到tomcat(第四话):把war包远程部署到tomcat

    上一节介绍了如何用Ant插件来打war包,本节接着介绍如何把build好的war自动部署到tomcat中 1.先安装 Deploy to container Plugin插件 2. 在构建后操作中添加 ...

  9. What is a working set and how do I use it?

    //http://www.avajava.com/tutorials/lessons/what-is-a-working-set-and-how-do-i-use-it.html Working se ...

  10. Mycat安装教程

      1.下载:   https://github.com/MyCATApache/Mycat-download 具体下载哪个版本以发布为准,推荐1.4,1.5.   2.安装:   安全前,在Linu ...