📜  Java中的快速失败和失败安全迭代器

📅  最后修改于: 2022-05-13 01:54:43.218000             🧑  作者: Mango

Java中的快速失败和失败安全迭代器

在这篇文章中,我将解释那些不能快速迭代的集合的行为方式。首先,在很多地方都没有给出故障安全的术语,因为Java SE 规范没有使用这个术语。我正在使用故障安全在快速失败和非快速失败迭代器之间进行隔离。
并发修改:编程中的并发修改意味着当另一个任务已经在它上面运行时同时修改一个对象。例如,在Java中,当另一个线程对其进行迭代时修改集合。如果检测到此行为,某些 Iterator 实现(包括 JRE 提供的所有通用集合实现的那些)可能会选择抛出ConcurrentModificationException

Java中的快速失败和失败安全迭代器

Java中的迭代器用于迭代 Collection 对象。如果对集合进行结构修改,Fail-Fast 迭代器会立即抛出ConcurrentModificationException 。结构修改意味着在线程迭代该集合时从集合中添加、删除任何元素。 ArrayList 上的迭代器,HashMap 类是快速失败迭代器的一些示例。
如果在迭代集合时对集合进行了结构修改,故障安全迭代器不会抛出任何异常。这是因为,它们在集合的克隆上操作,而不是在原始集合上操作,这就是为什么它们被称为故障安全迭代器。 CopyOnWriteArrayList 上的迭代器、ConcurrentHashMap 类是故障安全迭代器的示例。

Fail Fast Iterator 是如何工作的?

要知道集合是否在结构上被修改,快速失败迭代器使用名为modCount的内部标志,每次修改集合时都会更新该标志。快速失败迭代器在获得下一个值时检查modCount标志(即使用next( )方法),如果在创建此迭代器后发现modCount已被修改,则抛出ConcurrentModificationException

Java
// 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 cityCode = new HashMap();
        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");
        }
    }
}


Java
// 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 al = new ArrayList<>();
        al.add(1);
        al.add(2);
        al.add(3);
        al.add(4);
        al.add(5);
  
        Iterator 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);
            }
        }
    }
}


Java
// 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 list
            = new CopyOnWriteArrayList(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);
        }
    }
}


Java
// 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 map
            = new ConcurrentHashMap();
  
        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);
        }
    }
}


输出 :

India
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)
    at java.util.HashMap$KeyIterator.next(HashMap.java:1466)
    at FailFastExample.main(FailFastExample.java:18)

快速失败迭代器的要点:

  • 如果在迭代集合时修改了集合,这些迭代器将抛出 ConcurrentModificationException。
  • 他们使用原始集合来遍历集合的元素。
  • 这些迭代器不需要额外的内存。
  • 例如:ArrayList、Vector、HashMap 返回的迭代器。

注 1(来自 java-docs):不能保证迭代器的快速失败行为,因为一般来说,在存在不同步的并发修改的情况下不可能做出任何硬保证。快速失败的迭代器会尽最大努力抛出ConcurrentModificationException 。因此,编写一个依赖于这个异常的正确性的程序是错误的:迭代器的快速失败行为应该只用于检测错误。
注意 2:如果通过 Iterator remove()方法删除元素,则不会抛出异常。但是,如果通过特定的集合remove()方法删除,则会抛出ConcurrentModificationException 。下面的代码片段将演示这一点:

Java

// 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 al = new ArrayList<>();
        al.add(1);
        al.add(2);
        al.add(3);
        al.add(4);
        al.add(5);
  
        Iterator 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);
            }
        }
    }
}

输出 :

[1, 3, 4, 5]
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at FailFastExample.main(FailFastExample.java:28)

故障安全迭代器

首先,在很多地方都没有给出故障安全的术语,因为Java SE 规范没有使用这个术语。我使用这个术语来演示快速失败和非失败快速迭代器之间的区别。这些迭代器复制内部集合(对象数组)并遍历复制的集合。对迭代器所做的任何结构修改都会影响复制的集合,而不是原始集合。因此,原始集合在结构上保持不变

  • 故障安全迭代器允许在对集合进行迭代时对其进行修改。
  • 如果在迭代集合时修改了集合,这些迭代器不会抛出任何异常。
  • 他们使用原始集合的副本来遍历集合的元素。
  • 这些迭代器需要额外的内存来克隆集合。例如:ConcurrentHashMap、CopyOnWriteArrayList

Java中的故障安全迭代器示例:

Java

// 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 list
            = new CopyOnWriteArrayList(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);
        }
    }
}

输出:

1
3
5
8

此外,那些不使用快速失败概念的集合可能不一定会在内存中创建它的克隆/快照以避免 ConcurrentModificationException。例如,在 ConcurrentHashMap 的情况下,它不会对单独的副本进行操作,尽管它不是快速失败的。相反,它的语义被官方规范描述为弱一致( Java中的内存一致性属性)。下面的代码片段将演示这一点:
不创建单独副本的故障安全迭代器示例

Java

// 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 map
            = new ConcurrentHashMap();
  
        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);
        }
    }
}

输出

ONE : 1
FOUR : 4
TWO : 2
THREE : 3
SEVEN : 7

注意(来自 java-docs) :ConcurrentHashMap 返回的迭代器是弱一致的。这意味着该迭代器可以容忍并发修改,遍历构造迭代器时存在的元素,并且可以(但不保证)在构造迭代器之后反映对集合的修改。

Fail Fast Iterator 和 Fail Safe Iterator 的区别

主要区别在于故障安全迭代器不会抛出任何异常,与故障快速迭代器相反。这是因为它们在 Collection 的克隆而不是原始集合上工作,这就是它们被称为故障安全迭代器的原因。