fail-fast(快速失敗)機制和fail-safe(安全失敗)機制的介紹和區別


fail-fast和fail-safe的區別: 

fail-safe允許在遍歷的過程中對容器中的數據進行修改,而fail-fast則不允許。

 

fail-fast ( 快速失敗 )
fail-fast:直接在容器上進行遍歷,在遍歷過程中,一旦發現容器中的數據被修改了,會立刻拋出ConcurrentModificationException異常導致遍歷失敗。java.util包下的集合類都是快速失敗機制的, 常見的的使用fail-fast方式遍歷的容器有HashMap和ArrayList等。

在使用迭代器遍歷一個集合對象時,比如增強for,如果遍歷過程中對集合對象的內容進行了修改(增刪改),會拋出ConcurrentModificationException 異常.

fail-fast的出現場景
在我們常見的java集合中就可能出現fail-fast機制,比如ArrayList,HashMap。在多線程和單線程環境下都有可能出現快速失敗。

1、單線程環境下的fail-fast:
ArrayList發生fail-fast例子:

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    for (int i = 0 ; i < 10 ; i++ ) {
        list.add(i + "");
    }
    Iterator<String> iterator = list.iterator();
    int i = 0 ;
    while(iterator.hasNext()) {
        if (i == 3) {
             list.remove(3);
        }
        System.out.println(iterator.next());
        i ++;
    }
}

該段代碼定義了一個Arraylist集合,並使用迭代器遍歷,在遍歷過程中,刻意在某一步迭代中remove一個元素,這個時候,就會發生fail-fast

 

2、多線程環境下:

public class FailFastTest {
     public static List<String> list = new ArrayList<>();
 
     private static class MyThread1 extends Thread {
           @Override
           public void run() {
                Iterator<String> iterator = list.iterator();
                while(iterator.hasNext()) {
                     String s = iterator.next();
                     System.out.println(this.getName() + ":" + s);
                     try {
                       Thread.sleep(1000);
                     } catch (InterruptedException e) {
                        e.printStackTrace();
                     }
                }
                super.run();
           }
     }
 
     private static class MyThread2 extends Thread {
           int i = 0;
           @Override
           public void run() {
                while (i < 10) {
                     System.out.println("thread2:" + i);
                     if (i == 2) {
                         list.remove(i);
                     }
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     i ++;
                }
           }
     }
 
     public static void main(String[] args) {
           for(int i = 0 ; i < 10;i++){
               list.add(i+"");
           }
           MyThread1 thread1 = new MyThread1();
           MyThread2 thread2 = new MyThread2();
           thread1.setName("thread1");
           thread2.setName("thread2");
           thread1.start();
           thread2.start();
     }
}

 

fail-fast的原理:

fail-fast是如何拋出ConcurrentModificationException異常的,又是在什么情況下才會拋出?
我們知道,對於集合如list,map類,我們都可以通過迭代器來遍歷,而Iterator其實只是一個接口,具體的實現還是要看具體的集合類中的內部類去實現Iterator並實現相關方法。這里我們就以ArrayList類為例。在ArrayList中,當調用list.iterator()時,其源碼是: 

public Iterator<E> iterator() {
        return new Itr();

}

 

避免fail-fast的方法:

方法1
在單線程的遍歷過程中,如果要進行remove操作,可以調用迭代器的remove方法而不是集合類的remove方法

方法2

使用fail-safe機制,使用java並發包(java.util.concurrent)中的CopyOnWriterArrayList類來代替ArrayList,使用 ConcurrentHashMap來代替hashMap。

 

fail-safe ( 安全失敗 )
fail-safe:這種遍歷基於容器的一個克隆。因此,對容器內容的修改不影響遍歷。java.util.concurrent包下的容器都是安全失敗的,可以在多線程下並發使用,並發修改。常見的的使用fail-safe方式遍歷的容器有ConcerrentHashMap和CopyOnWriteArrayList等。

原理:

采用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先復制原有集合內容,在拷貝的集合上進行遍歷。由於迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改並不能被迭代器檢測到,所以不會觸發Concurrent Modification Exception。

缺點:基於拷貝內容的優點是避免了Concurrent Modification Exception,但同樣地,迭代器並不能訪問到修改后的內容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM