fail-fast 和 fail-safe
当一个或多个线程正在遍历一个集合(Collection),此时另一个线程修改了这个集合(添加,删除或修改)就称为并发修改。
.
官方文档在HashMap集合中对fail-fast的解释
意思就是:这个迭代器(Iterator)被创建后,除了迭代器自身的方法(remove)可以改变集合的 结构 ,其他情况改变了集合的结构,都将跑出一个 ConcurrentModificationException 异常。
从上面的源码,可以发现迭代器在执行 next() 等方法的时候,都会调用一个方法 checkForComodification() ,而这个方法就是检查 modCount 是否等于 expectedModCount ,如果不等于就抛出 ConcurrentModificationException 异常。
expectedModCount 这个变量的值在对象被创建的时候就赋予了一个固定的值 modCount ,这个值是不变的,当迭代器遍历元素的时候,如果 modCount 发生了改变,那就会抛出异常。
查看源码可以发现,当对集合进行增删操作都会 modCount++ 。
所以当我们对集合的元素的个数做出修改(添加、删除)的时候, modCount 的值就会发生改变,但对元素进行修改则不会改变 modCount 的值。
保证在并发修改的时候,对所有会影响到 modCount 发生改变的地方,加上同步锁(synchronized),或者使用同步类容器 Collections.synchronizedList 。
.
fail-safe: 任何对集合结构的修改都会在一个复制的集合上进行修改,因此不会抛出 ConcurrentModificationException 异常。
两个问题:
从源码可以看到,在对集合进行添加和删除元素的时候都进行加锁,然后让当前下标的元素添加或删除,最后将原数组的地址指向新的数组,完成复制。这里涉及到 CopyOnWrite机制 。
这样做不会出现 fail-fast ,但是对集合进行增删操作都需要加锁,影响效率。同时增加对象容量可能会导致 OOM 。
在遍历过程中,集合的元素并不一定是最终的元素集合,所以只能保证最终一致性。