迭代器的作用就是用来遍历集合的
那么,在遍历的过程中,是否允许其他人来修改集合?
有两种处理策略:Fail-Fast 与 Fail-Safe
1.Fail-Fast
fail-fast 一旦发现遍历的同时其它人来修改,则立刻抛异常
ArrayList 是 fail-fast 的典型代表,遍历的同时不能修改,尽快失败
测试代码
创建一个ArrayList集合并添加元素,遍历输出
添加断点,在遍历至 C 时,可以debug,模拟另一个线程,修改list,添加一个元素
查看 list 集合,由4个元素,变成了5个元素
放开断点,将 C 遍历输出后,下一轮循环时,报错(并发修改异常) java.util.ConcurrentModificationException ,
原理:
for循环会用到底层的迭代器,首次循环的时候,会创建一个迭代器对象
创建一个迭代器对象时,初始化迭代器的一些成员变量
modCount是 list数组 集合的成员变量,记录 list数组 被修改了几次(现在数值是4,因为add了4次)
expectedModCount 是迭代器的成员变量,记录迭代初始时,list数组 修改的次数
接下来会调用迭代器的hasNext()、next()方法,向下移动,每次调用next()方法时,会调用checkForComodification(),对 expectedModCount 做检查
当发现,迭代开始时的list数组修改次数(4),与list数组修改次数(5)不一致时,抛出异常
即,通过判断循环开始时list数组的修改次数,与循环中的list数组的修改次数是否相等,来判断循环过程中,list数组是否发生修改
如何查看list数组的modCount?
idea控制台,默认显示list数组的元素,进行一下修改
将List选项,改为Object选项,就能看到一些数的成员变量
2.Fail-Safe
fail-safe 发现遍历的同时其它人来修改,应当能有应对策略,例如牺牲一致性来让整个遍历运行完成
CopyOnWriteArrayList 是 fail-safe 的典型代表,遍历的同时可以修改,原理是读写分离
测试代码
创建一个CopyOnWriteArrayList集合并添加元素,遍历输出
添加断点,在遍历至 C 时,可以debug,模拟另一个线程,修改list,添加一个元素,查看 list 集合,由4个元素,变成了5个元素
放开全部断点,遍历输出了4个,但集合有5个元素
虽然没有报错,但是牺牲了一致性
原理:
在首次循环,会创建迭代器对象,不过对象变成了COWIterator
进入构造器的构造方法,会把当前正在遍历的数组记录下来,保存在snapshot成员变量中
依然创建集合,debug,在循环至C时,添加元素,发现list数组的元素变成了5个
进入迭代器看看,发现是初始的4个元素
为什么呢?
需要看下add的源码
注意这一句,add时,将原数组复制了一份,生成一个新数组,并长度+1,然后将新元素放到新数组的最后一个
每次添加新元素时,都会复制出一个新数组,添加结束后,就会把初始数组替换成新数组,但在遍历过程中,使用的是初始数组
添加是一个数组,遍历时另一数组,二者互补干扰