1. Fail Fast
a. 概念
在Java中可以使用Iterator来遍历集合对象。当一个线程使用iterator遍历集合元素时,如果集合的数据被修改了(增、删、改),支持Fail Fast的iterator会立刻抛出
ConcurrentModificationExcepton
异常。
- 支持Fail Fast迭代器的集合对象有ArrayList, HashMap, Vector等
b. 原理
fail-fast iterators使用了一个叫modCount
的内部标识来标志集合数据是否被修改,每当集合的数据有修改,modCount
就会被更新。每当iterator调用next()
获取下一个集合元素时,都会检查modCount
,如果发现在iterator被创建后,modCount
值被修改了,就会抛出ConcurrentModificationExcepton
异常。
// Java code to illustrate
// Fail Fast Iterator in Java
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class FailFastExample {
public static void main(String[] args)
{
Map<String, String> cityCode = new HashMap<String, String>();
cityCode.put("Delhi", "India");
cityCode.put("Moscow", "Russia");
cityCode.put("New York", "USA");
Iterator iterator = cityCode.keySet().iterator();
while (iterator.hasNext()) {
System.out.println(cityCode.get(iterator.next()));
// adding an element to Map
// exception will be thrown on next call
// of next() method.
cityCode.put("Istanbul", "Turkey");
}
}
}
OutPut
India
Exception in thread “main” java.util.ConcurrentModificationException
at java.util.HashMap H a s h I t e r a t o r . n e x t N o d e ( H a s h M a p . j a v a : 1442 ) a t j a v a . u t i l . H a s h M a p HashIterator.nextNode(HashMap.java:1442) at java.util.HashMap HashIterator.nextNode(HashMap.java:1442)atjava.util.HashMapKeyIterator.next(HashMap.java:1466)
at FailFastExample.main(FailFastExample.java:18)
c. 关注点
- 当使用iterator遍历集合时,如果集合元素被修改,就会抛出
ConcurrentModificationException
- 迭代器使用原始集合遍历集合元素
- 这些迭代器不需要额外的内存空间
- ArrayList, HashMap, Vector返回的迭代器支持Fail Fast
d. 注意
- 迭代器的快速失败行为不能得到保证,因为一般来说,在存在非同步并发修改时不可能做出任何硬性保证(比如类似ABA的问题可能会发生)。快速失败迭代器尽最大努力抛出
ConcurrentModificationException
。因此,如果要编写一个依赖于这个异常来保证其正确性的程序,那将是错误的:迭代器的快速失败行为应该仅用于检测错误。 - 如果通过
Iterator remove()
方法删除元素,则不会抛出异常。但是,在通过特定集合的remove()
方法进行删除时,将抛出ConcurrentModificationException
。下面是示例:
// Java code to demonstrate remove
// case in Fail-fast iterators
import java.util.ArrayList;
import java.util.Iterator;
public class FailFastExample {
public static void main(String[] args)
{
ArrayList<Integer> al = new ArrayList<>();
al.add(1);
al.add(2);
al.add(3);
al.add(4);
al.add(5);
Iterator<Integer> itr = al.iterator();
while (itr.hasNext()) {
if (itr.next() == 2) {
// will not throw Exception
itr.remove();
}
}
System.out.println(al);
itr = al.iterator();
while (itr.hasNext()) {
if (itr.next() == 3) {
// will throw Exception on
// next call of next() method
al.remove(3);
}
}
}
}
OutPut
[1, 3, 4, 5]
Exception in thread “main” java.util.ConcurrentModificationException
at java.util.ArrayList I t r . c h e c k F o r C o m o d i f i c a t i o n ( A r r a y L i s t . j a v a : 901 ) a t j a v a . u t i l . A r r a y L i s t Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList Itr.checkForComodification(ArrayList.java:901)atjava.util.ArrayListItr.next(ArrayList.java:851)
at FailFastExample.main(FailFastExample.java:28)
2. Fail Safe
a. 概念
首先,在许多地方都没有给出故障安全这个术语,因为Java SE规范没有使用这个术语。我使用这个术语来演示快速失败迭代器和非失败快速迭代器之间的区别。这些迭代器复制内部集合(对象数组)并遍历复制的集合。对迭代器所做的任何结构修改都会影响复制的集合,而不是原始的集合。因此,原始集合在结构上保持不变。
当一个线程使用iterator遍历集合元素时,如果集合的数据被修改了(增、删、改),支持Fail Safe的iterator不会异常。这是因为迭代器是在集合对象的拷贝上操作,而不是直接操作原集合对象。
- java.util.concurrent下的包如CopyOnWriteArrayList, ConcurrentHashMap都是支持Fail Safe的。
b. 原理
- 使用原始集合的副本遍历集合的元素,比如:CopyOnWriteArrayList
// Java code to illustrate
// Fail Safe Iterator in Java
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.Iterator;
class FailSafe {
public static void main(String args[])
{
CopyOnWriteArrayList<Integer> list
= new CopyOnWriteArrayList<Integer>(new Integer[] { 1, 3, 5, 8 });
Iterator itr = list.iterator();
while (itr.hasNext()) {
Integer no = (Integer)itr.next();
System.out.println(no);
if (no == 8)
// This will not print,
// hence it has created separate copy
list.add(14);
}
}
}
OutPut
1
3
5
8
- 另外,那些不使用fail-fast概念的集合可能不需要在内存中创建克隆/快照来避免
ConcurrentModificationException
。例如,在ConcurrentHashMap的情况下,它不会对单独的副本进行操作,尽管它不是Fail Fast。相反,它的语义被官方规范描述为弱一致性(Java中的内存一致性属性)。
ConcurrentHashMap返回的迭代器是弱一致的。这意味着该迭代器可以容忍并发修改,在构造迭代器时遍历元素,并且可以(但不保证)在构造迭代器后反映对集合的修改。
// Java program to illustrate
// Fail-Safe Iterator which
// does not create separate copy
import java.util.concurrent.ConcurrentHashMap;
import java.util.Iterator;
public class FailSafeItr {
public static void main(String[] args)
{
// Creating a ConcurrentHashMap
ConcurrentHashMap<String, Integer> map
= new ConcurrentHashMap<String, Integer>();
map.put("ONE", 1);
map.put("TWO", 2);
map.put("THREE", 3);
map.put("FOUR", 4);
// Getting an Iterator from map
Iterator it = map.keySet().iterator();
while (it.hasNext()) {
String key = (String)it.next();
System.out.println(key + " : " + map.get(key));
// This will reflect in iterator.
// Hence, it has not created separate copy
map.put("SEVEN", 7);
}
}
}
OutPut
ONE : 1
FOUR : 4
TWO : 2
THREE : 3
SEVEN : 7
3. Fail Fast 和 Fail Safe的区别
主要的区别是,与Fail Fast
相反,Fail Safe Iterator
不会抛出任何异常。这是因为它们在集合的克隆上工作,而不是在原始集合上工作,这就是它们被称为故障安全迭代器的原因。
参考文章:https://www.geeksforgeeks.org/fail-fast-fail-safe-iterators-java/