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