Java中的故障快速和故障安全迭代器
在Java Collections框架中,我们会看到ArrayList,HashMap之类的Collection返回一个称为快速失败迭代器的迭代器,而CopyOnWriteArrayList,ConcurrentHashMap等并发集合则返回一个称为故障安全迭代器的迭代器。在这篇文章中,我们将看到什么是Java中的故障快速迭代器和故障安全迭代器,以及Java中的故障快速迭代器和故障安全迭代器之间的区别。
Java中的快速失败迭代器
Java中的快速失败迭代器是一个迭代器,如果在创建迭代器之后的任何时候都对结构的基础集合进行了结构修改,则该迭代器会抛出ConcurrentModificationException,除非通过迭代器自己的remove或者add方法(适用于ListIterator)。
请注意,结构修改是添加或者删除一个或者多个元素的任何操作。仅在列表的情况下设置元素的值或者在映射的情况下仅更改与现有键关联的值的情况不被称为结构修改。
故障快速迭代器内部在Java中的工作
在ArrayList,HashSet之类的集合中,迭代器是快速失败的,实现类具有一个int变量modCount,该变量存储对该集合进行结构修改的次数。
每当在集合的迭代过程中获取下一个值时,将检查modCount和ExpectedModCount是否相等,如果两者不相等,则迭代器将抛出ConcurrentModificationException失败。
快速失败迭代器Java示例
让我们看一个示例,其中在迭代过程中将值添加到HashMap。
public class FailFastDemo { public static void main(String[] args) { Map<String, String> carMap = new HashMap<String, String>(); carMap.put("1", "Audi"); carMap.put("2", "BMW"); carMap.put("3", "Jaguar"); carMap.put("4", "Mini Cooper"); // iterating map Iterator<Map.Entry<String, String>> itr = carMap.entrySet().iterator(); while(itr.hasNext()) { Map.Entry<String, String> entry = itr.next(); System.out.println("Key is " + entry.getKey() + " Value is " + entry.getValue()); // adding value to Map carMap.put("5", "Mercedes"); } } }
输出:
Exception in thread "main" Key is 1 Value is Audi java.util.ConcurrentModificationException at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1498) at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1531) at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1529) at com.theitroad.FailFastDemo.main(FailFastDemo.java:19)
如我们所见,抛出ConcurrentModificationException是因为尝试在使用迭代器进行迭代时对结构进行结构化修改。
如前所述,仅修改返回快速失败迭代器的集合中的值不会导致抛出ConcurrentModificationException。
public class FailFastDemo { public static void main(String[] args) { Map<String, String> carMap = new HashMap<String, String>(); carMap.put("1", "Audi"); carMap.put("2", "BMW"); carMap.put("3", "Jaguar"); carMap.put("4", "Mini Cooper"); // iterating map Iterator<Map.Entry<String, String>> itr = carMap.entrySet().iterator(); while(itr.hasNext()) { Map.Entry<String, String> entry = itr.next(); if(entry.getKey().equals("3")) { // Modifying value for existing key carMap.put("3", "Mercedes"); } System.out.println("Key is " + entry.getKey() + " Value is " + entry.getValue()); } } }
输出:
Key is 1 Value is Audi Key is 2 Value is BMW Key is 3 Value is Mercedes Key is 4 Value is Mini Cooper
如我们所见,这里为现有键修改了值,这是可以的,并且不会引发ConcurrentModificationException。
使用迭代器的remove方法
我们可以使用迭代器的remove方法在迭代时删除元素,而不会引起ConcurrentModificationException。
public class FailFastDemo { public static void main(String[] args) { Map<String, String> carMap = new HashMap<String, String>(); carMap.put("1", "Audi"); carMap.put("2", "BMW"); carMap.put("3", "Jaguar"); carMap.put("4", "Mini Cooper"); // iterating map Iterator<Map.Entry<String, String>> itr = carMap.entrySet().iterator(); while(itr.hasNext()) { Map.Entry<String, String> entry = itr.next(); if(entry.getKey().equals("3")) { // removing using iterator's remove method itr.remove(); } } System.out.println("** After element removal **"); for(String key : carMap.keySet()){ System.out.println("Key is " + key + " Value is " + carMap.get(key)); } } }
输出:
** After element removal ** Key is 1 Value is Audi Key is 2 Value is BMW Key is 4 Value is Mini Cooper
Java中的故障安全迭代器
Java中的故障安全迭代器是在某个时间点对集合的快照进行工作的迭代器。因此,底层集合中的任何结构修改都不能保证在迭代时得到反映。
不同的并发集合的实现有所不同。例如,对于CopyOnWriteArrayList,迭代器方法在创建迭代器时使用对数组状态的引用。自创建迭代器以来,该迭代器将不会反映对该列表的添加,删除或者更改。
对于ConcurrentHashMap迭代器,Spliterators和Enumerations返回在创建迭代器/枚举时或者此后某个时刻反映哈希表状态的元素。因此,在ConcurrentHashMap的情况下,它可能会显示修改后的值。
故障安全迭代器ConcurrentHashMap示例
public class FailSafeDemo { public static void main(String[] args) { Map<String, String> carMap = new ConcurrentHashMap<String, String>(); carMap.put("1", "Audi"); carMap.put("2", "BMW"); carMap.put("3", "Jaguar"); carMap.put("4", "Mini Cooper"); // iterating map Iterator<Map.Entry<String, String>> itr = carMap.entrySet().iterator(); while(itr.hasNext()) { Map.Entry<String, String> entry = itr.next(); System.out.println("Key is " + entry.getKey() + " Value is " + entry.getValue()); carMap.putIfAbsent("5", "Mercedes"); } System.out.println("Size- " + carMap.size()); } }
输出:
Key is 1 Value is Audi Key is 2 Value is BMW Key is 3 Value is Jaguar Key is 4 Value is Mini Cooper Key is 5 Value is Mercedes Size- 5
同时显示在迭代地图时添加的元素。
故障安全迭代器CopyOnWriteArrayList示例
public class FailSafeDemo { public static void main(String[] args) { List<String> carList = new CopyOnWriteArrayList<>(); carList.add("Audi"); carList.add("BMW"); carList.add("Jaguar"); carList.add("Mini Cooper"); boolean addFlag = false; Iterator<String> itr = carList.iterator(); while(itr.hasNext()) { System.out.println(itr.next()); // add element to the list if(!addFlag){ carList.add("Mercedes"); addFlag = true; } } System.out.println("-- List after addition -- "); itr = carList.iterator(); while(itr.hasNext()) { System.out.println(itr.next()); } } }
输出:
Audi BMW Jaguar Mini Cooper -- List after addition -- Audi BMW Jaguar Mini Cooper Mercedes
在这里,我们可以看到该元素在迭代时被添加到CopyOnWriteArrayList中,但是没有像在CopyOnWriteArrayList迭代器中创建迭代器时使用对底层数组的引用那样反映在该迭代中。
故障快速与Java中的故障安全迭代器
如果在创建迭代器之后随时修改基础集合,则快速失败迭代器将引发ConcurrentModificationException。如果在创建迭代器之后随时修改基础集合,则故障安全迭代器不会引发ConcurrentModificationException。
如果使用快速失败的迭代器,则由于无法更改基础集合,因此无需在任何时间点克隆集合或者对集合进行快照。在使用故障安全迭代器的情况下,将使用基础集合的副本,或者使用任何时间点的快照快照,从而使故障安全迭代器具有较弱的一致性。
快速失败的迭代器具有自己的remove和add方法,可用于在迭代时修改集合。在基于实现的故障安全迭代器的情况下,可能不支持对迭代器本身的元素更改操作(删除,设置和添加),如CopyOnWriteArrayList一样。这些方法抛出UnsupportedOperationException。