Java集合的快速失败机制 “fail-fast”
Java集合中的快速失败(fail-fast)机制是指在使用迭代器对集合进行遍历时,如果集合的结构被修改(增加、删除元素等),则迭代器会立即抛出ConcurrentModificationException
异常,这种设计是为了避免不确定性和错误的结果。
工作原理
快速失败机制的实现依赖于集合中的一个称为modCount
的计数器。modCount
是集合中一个用来记录集合结构修改次数的变量。每当集合结构发生变化(如添加、删除元素)时,modCount
的值就会增加。当创建迭代器时,迭代器会记录下当前的modCount
值作为自己的一个状态值(称为expectedModCount
)。在迭代过程中,每次使用迭代器获取下一个元素之前,都会检查modCount
是否等于expectedModCount
,如果不等,说明集合的结构在迭代过程中被修改了,迭代器就会抛出ConcurrentModificationException
。
影响
快速失败机制存在于Java集合框架的很多实现中,如ArrayList
、HashMap
等。但是,并非所有的集合实现都是快速失败的。例如,ConcurrentHashMap
和CopyOnWriteArrayList
等并发集合就采用了不同的策略来允许并发修改。
应对策略
当需要在遍历过程中修改集合时,可以采用以下几种策略来避免ConcurrentModificationException
:
-
使用并发集合:选择支持并发修改的集合实现,如
ConcurrentHashMap
、CopyOnWriteArrayList
等。 -
使用迭代器的
remove
方法:如果只是需要在迭代时删除元素,可以使用迭代器自己的remove
方法,而不是集合的remove
方法。迭代器的remove
方法会更新expectedModCount
值,从而避免异常。 -
收集需要修改的元素,迭代完成后统一处理:在迭代过程中,将需要修改的元素收集起来,迭代完成后再对集合进行统一的修改。
示例
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String language = iterator.next();
if ("Python".equals(language)) {
iterator.remove(); // 使用迭代器的remove方法
}
}
快速失败机制是Java集合设计中的一个重要特性,它有助于及早发现并发修改的问题,但同时也要求开发者在使用集合时更加注意操作的安全性。
ConcurrentHashMap和CopyOnWriteArrayList等并发集合就采用了不同的策略来允许并发修改
ConcurrentHashMap
和CopyOnWriteArrayList
是Java并发包java.util.concurrent
中提供的两种线程安全的集合,它们采用了不同的策略来允许并发的读写操作,从而避免了快速失败(fail-fast)机制导致的ConcurrentModificationException
。
ConcurrentHashMap
ConcurrentHashMap
是一个线程安全的哈希表,用于替代同步的HashMap
(通过Collections.synchronizedMap
方法包装得到)。它采用了分段锁(Segmentation Locking)的策略来提高并发访问的效率。
- 分段锁:
ConcurrentHashMap
内部维护了一个分段锁的数组,每个分段锁管理哈希表的一部分数据。当进行插入、删除等修改操作时,只需要锁定对应的分段,而不是整个哈希表。这样,不同的线程可以同时操作哈希表的不同部分,从而提高并发性能。 - 无锁读取:对于读操作,
ConcurrentHashMap
允许多个线程同时无锁访问,因为它对数据的修改操作能够保证数据的一致性和可见性。
CopyOnWriteArrayList
CopyOnWriteArrayList
是一个线程安全的列表,用于替代同步的ArrayList
。它采用了写时复制(Copy-On-Write)的策略来实现线程安全。
- 写时复制:当需要修改列表(如添加、删除、设置元素)时,
CopyOnWriteArrayList
会先复制一份当前的数据,然后在这份复制的数据上进行修改操作,修改完成后再将原数据引用指向新的复制数据。这样,读操作总是在不变的数据快照上进行,无需加锁,从而实现了高效的并发读取。 - 适用场景:由于写操作需要复制整个底层数组,因此
CopyOnWriteArrayList
适用于读多写少的并发场景。
总结
ConcurrentHashMap
通过分段锁的策略,实现了高效的并发更新操作,同时允许多线程无锁地进行读取操作。CopyOnWriteArrayList
通过写时复制的策略,保证了并发读的高效性,适用于读多写少的场景。- 这两种集合都避免了快速失败机制,提供了更强的并发性能和数据一致性保证。然而,它们的实现策略也决定了各自的使用场景和性能开销,开发者在选择使用时需要根据实际需求做出合理的决策。