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,但同樣地,迭代器並不能訪問到修改后的內容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的。