快速失敗機制--fail-fast

来源:https://www.cnblogs.com/duzhentong/archive/2018/03/18/8597512.html
-Advertisement-
Play Games

fail fast 機制是Java集合(Collection)中的一種錯誤機制。當多個線程對同一個集合的內容進行操作時,就可能會產生fail fast(快速失敗)事件。例如:當某一個線程A通過iterator去遍歷某集合的過程中,若該集合的內容被其他線程所改變了;那麼線程A訪問集合時,就會拋出Con ...


fail-fast 機制是Java集合(Collection)中的一種錯誤機制。當多個線程對同一個集合的內容進行操作時,就可能會產生fail-fast(快速失敗)事件。例如:當某一個線程A通過iterator去遍歷某集合的過程中,若該集合的內容被其他線程所改變了;那麼線程A訪問集合時,就會拋出ConcurrentModificationException異常,產生fail-fast事件。

迭代器的快速失敗行為無法得到保證,它不能保證一定會出現該錯誤,但是快速失敗操作會盡最大努力拋出ConcurrentModificationException異常。

註意:上面所說的是在多線程環境下會發生fail-fast事件,但是單線程條件下如果違反了規則也是會產生fail-fast事件的

在文檔中有這麼一段話:編寫的程式依賴於快速失敗機制產生的異常是不對的,迭代器的快速檢測機制僅僅用於檢測錯誤。

分別用兩段程式測試快速失敗機制產生的原因:

單線程環境:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Created with IDEA
 *
 * @author DuzhenTong
 * @Date 2018/3/18
 * @Time 17:34
 */
public class FailFast {
    public static void main(String[] args) {
        List list = new ArrayList();
        for (int i = 0; i < 10; i++) {
            list.add(i);
        }
        iterator(list);
    }

    public static void iterator(List list) {
        Iterator it = list.iterator();
        int index = 0;
        while (it.hasNext()) {
            if (index == 6) {
                list.remove(index);
            }
            index++;
            System.out.println(it.next());
        }
    }
}

輸出結果:

0
1
2
3
4
5
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)
    at java.util.ArrayList$Itr.next(ArrayList.java:791)
    at FailFast.iterator(FailFast.java:29)
    at FailFast.main(FailFast.java:18)
多線程環境:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Created with IDEA
 *
 * @author DuzhenTong
 * @Date 2018/3/18
 * @Time 17:59
 */
public class FailFast1 {

    public static List list = new ArrayList();
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            list.add(i);
        }
        new ThreadA().start();
        new ThreadB().start();
    }

    public static class ThreadA extends Thread {
        @Override
        public void run() {
            Iterator it = list.iterator();
            while (it.hasNext()) {
                System.out.println("集合遍歷中……:"+it.next());
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static class ThreadB extends Thread {
        @Override
        public void run() {
            int index = 0;
            while (index != 10) {
                System.out.println("線程等待中……:"+index);
                if (index == 3) {
                    list.remove(index);
                }
                index++;
            }
        }
    }
}

輸出結果:

線程等待中……:0
集合遍歷中……:0
線程等待中……:1
線程等待中……:2
線程等待中……:3
線程等待中……:4
線程等待中……:5
線程等待中……:6
線程等待中……:7
線程等待中……:8
線程等待中……:9
Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)
    at java.util.ArrayList$Itr.next(ArrayList.java:791)
    at FailFast1$ThreadA.run(FailFast1.java:28)

上面的程式已經說明瞭為什麼會發生fail-fast事件(快速失敗),在多線程條件下,一個線程正在遍歷集合中的元素,這時候另一個線程更改了集合的結構,程式才會拋出ConcurrentModificationException,在單線程條件下也是在遍歷的時候,這時候更改集合的結構,程式就會拋出ConcurrentModificationException。
要具體知道為什麼會出現fail-fast,就要分析源碼,fail-fast出現是在遍歷集合的時候出現的,也就是對集合進行迭代的時候,對集合進行迭代的時候都是操作迭代器,集合中的內部類:(ArrayList源碼)

private class Itr implements Iterator<E> {
        int cursor;       
        int lastRet = -1; 
        int expectedModCount = modCount;//---------------------1

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            //………此處代碼省略…………
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

上面的代碼中我們可以看到拋出ConcurrentModificationException異常,上面的next(),remove()都會調用checkForComodification()方法檢查兩個變數的值是否相等,不相等就會拋出異常。在上面程式中的數字1處:

int expectedModCount = modCount;

modCount的值賦值給expectedModCount,知道modCount這個值的含義是什麼?為什麼會發生改變?原因就會找到了。
源碼點進去,發現這個modCount變數並不在ArrayList類中,而在AbstractList中,作為一個成員變數。

protected transient int modCount = 0;

接下來分析源碼,看最常用的方法

ArrayList中add方法:
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

調用的ensureCapacityInternal方法:

private void ensureCapacityInternal(int minCapacity) {
        modCount++;//modCount自增————————————
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
ArrayList中的remove方法:
public E remove(int index) {
        rangeCheck(index);

        modCount++;//modCount自增——————————————
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work

        return oldValue;
    }
ArrayList中的clear方法:
public void clear() {
        modCount++;//modCount自增——————————————

        // Let gc do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

分析了源碼就知道原因是什麼了,凡是涉及到改變了集合的結構(改變元素的個數)的操作(包括增加,移除或者清空等)modCount這個變數都會自增,在獲得迭代對象的時候,先把這個modCount變數賦值給expectedModCount,在迭代的時候每次都會檢查這個變數是否與expectedModCount一致,因為如果是在集合中添加或者刪除元素modCount的值都會發生改變。

解決方法:

  • 對於涉及到更改集合中元素個數的操作通通加上synchronized,或者利用Collections.synchronizedList強制他們的操作都是同步的。
  • 使用CopyOnWriteArrayList來替換ArrayList

這裡在網上看到很多的文章都是這麼說的:為什麼CopyOnWriteArrayList可以做到不會發生fail-fast?因為CopyOnWriteArrayList所有可變操作(add、set 等等)都是通過對底層數組進行一次新的複製來實現的。

可以分析源碼(下麵的1,2處)

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();//————————1
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);//————————2
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

我的看法:原因不止有CopyOnWriteArrayList的add、set、remove等會改變原數組的方法中,都是先copy一份原來的array,再在copy數組上進行add、set、remove操作,這就才不影響COWIterator那份數組。

為什麼沒有記錄修改次數的值或者說不比較modCount也能做到記憶體的一致性呢?
在上面的代碼1處,調用了getArray()方法,看源碼:

final Object[] getArray() {
        return array;
    }
private volatile transient Object[] array;

因為getArray()返回的array的類型是用volatile修飾的,volatile類型的(強制記憶體一致性)
具體可以看我的另一篇關於volatile的:volatile關鍵字解析

參考文章:http://cmsblogs.com/?p=1220


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Problem Description The GeoSurvComp geologic survey company is responsible for detecting underground oil deposits. GeoSurvComp works with one large re ...
  • 在業務中遇到了需要判斷文件是否存在的需求,所以順便整理一下python判斷文件是否存在的方法。在操作文件前,檢查文件是否存在也是一個良好的編程習慣。通常來說,有三種常見方式來判斷文件夾或文件是否存在,分別是os模塊,try語句和pathlib模塊。 os模塊 os模塊中的os.path.exists ...
  • 基本術語: 節點的度:書中某一節點擁有的子節點數量。 數的度:該樹中所有節點的度的最大值。 葉節點(終端節點):度為零的節點。 分支節點(非終端節點):度不為零的節點。 根節點(開始節點):樹中的第一個節點。 內部節點:樹中除了根節點之外的節點。 節點的層數:若根節點層數為1,根節點的第n代子節點的 ...
  • manifest文件是用來描述jar包的,它描述了該jar包的代碼是誰的,什麼版本,使用什麼版本的類庫等等。。。。具體如下: 1、基礎格式 manifest 文件的格式是很簡單的,每一行都是 名-值 對應的:屬性名開頭,接著是 ":" ,然後是屬性值,每行最多72個字元,如果需要增加,你可以在下一行 ...
  • 無意中看到了這篇文章,占時還沒用到這個知識,主要解決網頁訪問量增加到一定數量級帶來的網站停滯問題。前些時候也學了JVM相關知識,先碼一下,以後也許有用 以下文章轉至原文http://blog.csdn.net/u014236541/article/details/50008047 JVM參數調優是個 ...
  • 基於java版本的掃碼支付開發 最近做了一個電商,其中有涉及關於支付的問題,花了點小小的時間。因此寫了一個小demo,不能說完美,但是能實現下訂單掃碼支付功能。這裡我選擇的技術是J2EE中的servlet和jsp,支付環境採用的是支付寶中的沙箱環境(基於本人沒有企業級或者個人營業執照,無法申請支付接 ...
  • 前言 在開始工作至今,學習各種各樣的技術之中發現自己的很多Java的基礎知識都忘了⊙﹏⊙b汗。。。 而且越是學習越是發現Java基礎的重要性,所以準備單獨抽一下時間進行Java基礎的重新學習。在重新學習的時候,對這些又有了不同的感悟,於是準備將這些都記載下來,並整理成博客,希望能夠幫助那些需要的人。 ...
  • 1.編程語言的分類 機器語言:二進位指令編程,直接操作硬體,運行效率高,開發效率低 彙編語言:用英文標簽代替二進位指令,也是直接操作硬體,運行效率高,開發效率低 高級語言:用人類的字元編寫程式,電腦無法理解,必須翻譯為電腦能夠理解的語言,才能執行,分為兩種 編譯型,解釋型 編譯型:如c語言,編譯 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...