问题引入:

我们知道,迭代器的作用就是为了遍历集合:set集合、list集合等。

那么在迭代器工作的过程中,是否允许别的人修改这个集合呢?
如果允许,我在遍历时候别人把数据改动了怎么办?
如果不允许,以什么情况告诉它不能以什么样的形式去修改?

以上的两个问题就牵扯到了迭代器的两种不同处理策略:fail-fast、fail-safe。


简要介绍两种机制:

fail-fast:

一旦发现遍历的同时其他人来修改,应该立刻抛出异常,不允许这种情况发生。

fail-safe:

如果发现遍历的同时又其他人来修改了,应该能有应对策略,例如牺牲一致性来让整个遍历继续运行完成,不让这个遍历报错。


重点来了:

1、举例fail-fast:ArrayList<>底层

那么对于ArrayList来讲,它的迭代器就是属于failFast,也就是它不允许一边遍历一边修改。

如果迭代时修改了集合,会爆出ConcurrentModificationException——并发修改异常,让这个循环达咩进行下去。

fail-fast的底层实现的比较简单,就是记录了开始时的修改次数,如果在循环的过程中这个修改次数不一致了,就会抛出异常,停止循环。

2、举例fail-safe:CopyOnWriteArrayList<>底层

而对于CopyOnWriteArrayList这个集合来讲,它的迭代器就是属于fail-safe,也就是他运行时如果有修改,会牺牲一致性让整个遍历继续完成,不会报错。

它的底层,list数组和迭代器的数组是两个不同的数组,迭代器中保存的是原来的数组,list集合中保存的是修改后的数组。

为什么会出现两个数组,是因为它的底层add方法,会首先getArray拿到旧的数组,拿到旧的数组以后,会复制一份新的数组,并且扩容的时候也是加载的新的数组里面。
也就是说,遍历的时候迭代器遍历的还是旧的数组,修改的时候用的是新的数组,二者互不干扰。
当然,在遍历之后,会把旧的数组替换成新的数组,遍历遍历的还是旧数组,只不过在遍历之后那个旧数组就没有用处了。


总结:

ArrayList是fail-fast的典型代表,遍历的同时不能修改,尽快失败。
CopyOnWriteArrayList是fail-safe的典型代表,遍历的同时可以修改,原理是读写分离。也就是一个数组用来读,复制一份新数组用来修改。

留一个思考:vector是哪个机制呢?

回答一下:Vector 是 fail-fast 机制的代表。
虽然 Vector 是线程安全的(方法加了 synchronized),但是它的 迭代器 并没有做额外的安全处理,而是和 ArrayList 一样,依然是 fail-fast。
底层逻辑是一样的:
其内部保存一个计数变量,每次迭代器调用方法会检查当前的计数变量是否和保存的计数变量一致。
如果不一致,说明在迭代期间集合被别人修改过了,就会抛出 ConcurrentModificationException。


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐