最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

ConcurrentModificationException日志關(guān)鍵字報(bào)警思考分析

 更新時(shí)間:2023年12月11日 11:35:33   作者:James_Shangguan  
本文將記錄和分析日志中的ConcurrentModificationException關(guān)鍵字報(bào)警,還有一些我的思考,有需要的朋友可以借鑒參考下,希望能夠有所幫助

一、背景

近期,在日常的日志關(guān)鍵字報(bào)警分析時(shí),發(fā)現(xiàn)我負(fù)責(zé)的一個(gè)電商核心系統(tǒng)在某時(shí)段存在較多ConcurrentModificationException異常日志,遂進(jìn)行分析和改進(jìn),下面是我的一些思考。

1.1 系統(tǒng)架構(gòu)

一直以來(lái),無(wú)狀態(tài)的服務(wù)都被當(dāng)作分布式服務(wù)設(shè)計(jì)的最佳實(shí)踐。因?yàn)闊o(wú)狀態(tài)的服務(wù)對(duì)于擴(kuò)展性和運(yùn)維方面有著得天獨(dú)厚的優(yōu)勢(shì),可以隨意地增加和減少節(jié)點(diǎn)。本系統(tǒng)的整體架構(gòu)可以認(rèn)為是由一個(gè)MQ應(yīng)用、一個(gè)RPC應(yīng)用底層存儲(chǔ)組成。

RPC應(yīng)用是無(wú)狀態(tài)服務(wù),對(duì)外提供常用的查詢和操作接口;采用雙機(jī)房部署,每個(gè)機(jī)房10*8C16G;

MQ應(yīng)用是無(wú)狀態(tài)服務(wù),負(fù)責(zé)消費(fèi)MQ消息,在消費(fèi)過(guò)程中會(huì)調(diào)用該RPC應(yīng)用提供方法;采用雙機(jī)房部署,每個(gè)機(jī)房5*8C16G;

底層存儲(chǔ)用的是數(shù)據(jù)庫(kù)集群和緩存集群,大概如圖所示:

1.2 關(guān)鍵代碼

MyRpcService 對(duì)外提供RPC服務(wù),getList 方法可以根據(jù)入?yún)⒅械臓顟B(tài)進(jìn)行查詢,由于業(yè)務(wù)需要,需要對(duì)入?yún)⒌臓顟B(tài)進(jìn)行排序,實(shí)現(xiàn)部分關(guān)鍵代碼如下:

public class MyRpcServiceImpl implements MyRpcService{
    @Override
    public BaseResult getList(ListParam listParam) {
        BaseResult baseResult = new BaseResult();
        List<Integer> states = listParam.getStateList();
        // 省略大段代碼
        KeyUtil.getKeyString(states);
        // 省略大段代碼
        baseResult.setSuccess(true);
        return baseResult;
    }
}

KeyUtil 是一個(gè)工具類,getKeyString 方法對(duì)入?yún)⒌?code>itemList進(jìn)行排序使用的是Java集合框架內(nèi)置的sort 方法,代碼如下:

public class KeyUtil {
    public static String getKeyString(List<Integer> itemList) {
        String result = "";
        //省略代碼
        Collections.sort(itemList);
        //省略代碼
        return result;
    }
}

MyMqConsumer是MQ消費(fèi)者,負(fù)責(zé)監(jiān)聽(tīng)消息進(jìn)行消費(fèi)。在消費(fèi)邏輯中,會(huì)調(diào)用MyRpcServicegetList() 方法進(jìn)行狀態(tài)查詢,因?yàn)椴樵兊臓顟B(tài)是固定的,所以在Consumer類中定義了static final 類型的stateList ,關(guān)鍵代碼如下:

public class MyMqConsumer implements MessageListener{
    public static final List<Integer> stateList = Stream.of(1).collect(Collectors.toList());
    @Resource
    private MyRpcService myRpcService;
    @Override
    public void onMessage(List<Message> messageList) {
        // 省略代碼
        for (Message message : messageList) {
            // 省略其他代碼
            ListParam listParam = new ListParam();
            listParam.setStateList(stateList);
            BaseResult result = myRpcService.getList(listParam);
            // 省略其他代碼
        }
    }
}

二、原因分析

看了上面的系統(tǒng)架構(gòu)和關(guān)鍵代碼,不知道你有沒(méi)有發(fā)現(xiàn)問(wèn)題?可以先拋開(kāi)設(shè)計(jì)和代碼實(shí)現(xiàn)方面的問(wèn)題不談,只看這樣的代碼能不能正常執(zhí)行,得到正確的業(yè)務(wù)結(jié)果。

既然這么問(wèn)了,當(dāng)然會(huì)有問(wèn)題:在高并發(fā)環(huán)境下,MQ應(yīng)用在消費(fèi)消息時(shí),調(diào)用RPC服務(wù)查詢時(shí)可能會(huì)拋出異常,從而觸發(fā)MQ異常重試,至于對(duì)業(yè)務(wù)有沒(méi)有影響,得具體問(wèn)題具體分析了。

ERROR 執(zhí)行流程時(shí)出錯(cuò)
java.util.ConcurrentModificationException:null
at java.util.ArrayList.forEach(ArrayList.java:1260)~[:?1.8.0_192]
at com.shangguan.test.util.KeyUtil.getKeyString(KeyUtil.java:10)
...

2.1 ArrayList源碼

從日志中可以看到,ConcurrentModificationExceptionjava.util.ArrayList類里面的forEach方法拋出來(lái)的,源碼如下:

    @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

在該方法中,內(nèi)部會(huì)維護(hù)一個(gè)expectedModCount變量,賦值為modCount,在每次迭代過(guò)程中,迭代器會(huì)檢查expectedModCount是否等于當(dāng)前的modCount。如果不等,說(shuō)明在迭代過(guò)程中ArrayList的結(jié)構(gòu)發(fā)生了修改,迭代器會(huì)拋出ConcurrentModificationException異常。這種設(shè)計(jì)可以確保在多線程環(huán)境下,當(dāng)一個(gè)線程修改ArrayList時(shí),其他線程在迭代過(guò)程中可以立即發(fā)現(xiàn)這種修改,從而避免潛在的數(shù)據(jù)不一致問(wèn)題。

再可以看下源碼中modCount的注釋,大意是:

modCount表示ArrayList自從創(chuàng)建以來(lái)結(jié)構(gòu)上發(fā)生的修改次數(shù)。結(jié)構(gòu)修改是指改變列表大小的修改,或者以其他方式擾亂列表,使正在進(jìn)行的迭代可能產(chǎn)生不正確的結(jié)果。

modCount字段用于iteratorlistIterator方法返回的迭代器(或列表迭代器)。如果這個(gè)字段的值在迭代過(guò)程中發(fā)生意外的變化,迭代器(或列表迭代器)將在next、remove、previous、set或add操作時(shí)拋出ConcurrentModificationException異常。這提供了fail-fast(快速失?。┬袨?,而不是在迭代過(guò)程中遇到并發(fā)修改時(shí)具有不確定性。

子類可以選擇使用這個(gè)字段。如果子類希望提供fail-fast迭代器(和列表迭代器),那么它只需在其add(int, E)remove(int)方法(以及覆蓋的任何其他導(dǎo)致列表結(jié)構(gòu)修改的方法)中遞增此字段。單次調(diào)用add(int, E)remove(int)應(yīng)該在此字段上增加不超過(guò)1次,否則迭代器(和列表迭代器)將拋出虛假的ConcurrentModificationException。如果實(shí)現(xiàn)不希望提供fail-fast迭代器,可以忽略此字段。

2.2 線程安全問(wèn)題

有個(gè)有趣的現(xiàn)象是,這個(gè)異常日志僅存在MQ應(yīng)用中,這是為什么呢?

這其實(shí)是一個(gè)多線程問(wèn)題。我們知道,static對(duì)象是在類加載時(shí)創(chuàng)建的全局對(duì)象,它們的生命周期與類的生命周期相同。static對(duì)象在程序啟動(dòng)時(shí)創(chuàng)建,在程序結(jié)束時(shí)銷毀。這意味著static對(duì)象在多個(gè)線程之間共享的,可能存在線程安全問(wèn)題。

翻回去仔細(xì)看下代碼,可以看到MyMqConsumer定義的stateList是static類型的,是否是否存在線程安全問(wèn)題呢?

在流量較低的情況下,多個(gè)消息不在同一時(shí)刻到達(dá),每個(gè)線程處理消息將不會(huì)爭(zhēng)奪static對(duì)象,所以不會(huì)有問(wèn)題;

當(dāng)流量較大情況下,有多個(gè)消息可能在同一時(shí)刻到達(dá),每個(gè)線程處理過(guò)程中都會(huì)對(duì)stateList進(jìn)行賦值,調(diào)用遠(yuǎn)程RPC接口,它們之間將會(huì)爭(zhēng)奪static對(duì)象,可能存在問(wèn)題。例如上圖中右半部分,線程1還沒(méi)有處理完消息1時(shí),線程2就開(kāi)始爭(zhēng)搶,那么就可能使ArrayList中modCount != expectedModCount條件滿足,從而拋出異常。

三、改進(jìn)思考

3.1 本問(wèn)題的優(yōu)化

經(jīng)過(guò)上述分析,已經(jīng)清楚問(wèn)題的產(chǎn)生原因了。對(duì)于本問(wèn)題的優(yōu)化,其實(shí)也比較簡(jiǎn)單。有如下兩種方式可供選擇:

1.  在MyMqConsumer調(diào)用RPC查詢的入?yún)?,使?strong>new List來(lái)替代原來(lái)的類中定義好的static對(duì)象;

2.  修改KeyUtil代碼,淺拷貝傳入的itemList,再進(jìn)行排序

3.2類似問(wèn)題的發(fā)現(xiàn)和改進(jìn)

本問(wèn)題已經(jīng)修復(fù),那類似的問(wèn)題是否可以避免或者減少,將是接下來(lái)值得思考的一個(gè)問(wèn)題。為了減少這類問(wèn)題發(fā)生,我結(jié)合平時(shí)工作過(guò)程中的幾個(gè)階段,認(rèn)為可以從以下幾個(gè)方面進(jìn)行改進(jìn):

  • 開(kāi)發(fā)

開(kāi)發(fā)過(guò)程中,開(kāi)發(fā)人員需要提升認(rèn)知和水平,注意代碼中可能存在的線程問(wèn)題;注意編寫(xiě)單元測(cè)試,可以通過(guò)模擬多線程環(huán)境來(lái)檢測(cè)潛在的問(wèn)題。

  • 代碼評(píng)審

開(kāi)發(fā)完成的代碼一定需要進(jìn)行代碼評(píng)審,評(píng)審過(guò)程中架構(gòu)師需要發(fā)揮自己豐富的開(kāi)發(fā)經(jīng)驗(yàn)和較強(qiáng)的代碼直覺(jué),“火眼金睛”,發(fā)現(xiàn)代碼中的漏洞;當(dāng)然這對(duì)評(píng)審人員的要求很高,因?yàn)閮H通過(guò)改動(dòng)的幾行代碼發(fā)現(xiàn)問(wèn)題確實(shí)是一件很有挑戰(zhàn)的事情。如果要有一些自動(dòng)化工具或者插件,則可以起到事半功倍的效果。這里其實(shí)我還沒(méi)有調(diào)研相關(guān)的工具,如果有大佬有相關(guān)經(jīng)驗(yàn)歡迎評(píng)論交流。

  • 測(cè)試

測(cè)試階段除了驗(yàn)證正常的業(yè)務(wù)功能,還需要進(jìn)行集成測(cè)試和性能測(cè)試。在集成測(cè)試中,將多個(gè)模塊組合在一起,測(cè)試整個(gè)系統(tǒng)在多線程環(huán)境中的行為,有助于發(fā)現(xiàn)模塊之間的交互問(wèn)題。除了繼承測(cè)試,有時(shí)還需要性能測(cè)試,性能測(cè)試可以發(fā)現(xiàn)潛在的競(jìng)爭(zhēng)條件、死鎖、資源爭(zhēng)用等多線程問(wèn)題。

四、小結(jié)

最后,我簡(jiǎn)單總結(jié)一下本文內(nèi)容。本文主要記錄和分析日志中的ConcurrentModificationException關(guān)鍵字報(bào)警,首先介紹了系統(tǒng)整體架構(gòu)和關(guān)鍵代碼;然后從ArrayList源碼和線程安全兩個(gè)方面分析問(wèn)題產(chǎn)生原因,最后我提出了修復(fù)該問(wèn)題的方案和類似問(wèn)題的思考,希望對(duì)大家有幫助。

以上就是ConcurrentModificationException日志關(guān)鍵字報(bào)警思考分析的詳細(xì)內(nèi)容,更多關(guān)于ConcurrentModificationException報(bào)警的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java壓縮文件與刪除文件的示例代碼

    java壓縮文件與刪除文件的示例代碼

    這篇文章主要介紹了java壓縮文件與刪除文件的示例代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-08-08
  • Java實(shí)現(xiàn)手寫(xiě)乞丐版線程池的示例代碼

    Java實(shí)現(xiàn)手寫(xiě)乞丐版線程池的示例代碼

    在這篇文章當(dāng)中我們主要介紹實(shí)現(xiàn)一個(gè)非常簡(jiǎn)易版的線程池,深入的去理解其中的原理,麻雀雖小,五臟俱全,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)學(xué)習(xí)吧
    2022-10-10
  • Java中的static關(guān)鍵字你了解多少

    Java中的static關(guān)鍵字你了解多少

    這篇文章主要為大家詳細(xì)介紹了Java中的static關(guān)鍵字,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-02-02
  • Spring cloud踩坑記錄之使用feignclient遠(yuǎn)程調(diào)用服務(wù)404的方法

    Spring cloud踩坑記錄之使用feignclient遠(yuǎn)程調(diào)用服務(wù)404的方法

    這篇文章主要給大家介紹了關(guān)于Spring cloud踩坑記錄之使用feignclient遠(yuǎn)程調(diào)用服務(wù)404的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • scala 匿名函數(shù)案例詳解

    scala 匿名函數(shù)案例詳解

    Scala支持一級(jí)函數(shù),函數(shù)可以用函數(shù)文字語(yǔ)法表達(dá),即(x:Int)=> x + 1,該函數(shù)可以由一個(gè)叫作函數(shù)值的對(duì)象來(lái)表示,這篇文章主要介紹了scala 匿名函數(shù)詳解,需要的朋友可以參考下
    2023-03-03
  • Spring Boot4使用Sentinel進(jìn)行限流的操作方法

    Spring Boot4使用Sentinel進(jìn)行限流的操作方法

    Sentinel是阿里巴巴開(kāi)源的流量控制組件,用于保障微服務(wù)架構(gòu)的穩(wěn)定性,它提供了多種規(guī)則類型,本文還介紹了如何在Spring Boot 4中集成Sentinel,以及Sentinel在不同版本的Spring Boot和Spring Cloud中的兼容性情況,感興趣的朋友跟隨小編一起看看吧
    2025-12-12
  • 一文帶你你搞懂Java的3種IO模型

    一文帶你你搞懂Java的3種IO模型

    在Java中,一共有三種IO模型,分別是阻塞IO(BIO)、非阻塞IO(NIO)和異步IO(AIO),本文將給大家詳解的介紹這三種IO模型,文中有相關(guān)的代碼示例,需要的朋友可以參考下
    2023-05-05
  • java 漢諾塔Hanoi遞歸、非遞歸(仿系統(tǒng)遞歸)和非遞歸規(guī)律 實(shí)現(xiàn)代碼

    java 漢諾塔Hanoi遞歸、非遞歸(仿系統(tǒng)遞歸)和非遞歸規(guī)律 實(shí)現(xiàn)代碼

    漢諾塔(Hanoi) 算法Java實(shí)現(xiàn)。通過(guò)三個(gè)函數(shù),分別對(duì)Hanoi進(jìn)行遞歸、非遞歸和非遞歸規(guī)律實(shí)現(xiàn)。
    2013-05-05
  • SpringBoot接口如何對(duì)參數(shù)進(jìn)行校驗(yàn)

    SpringBoot接口如何對(duì)參數(shù)進(jìn)行校驗(yàn)

    這篇文章主要介紹了SpringBoot接口如何對(duì)參數(shù)進(jìn)行校驗(yàn),在以SpringBoot開(kāi)發(fā)Restful接口時(shí),?對(duì)于接口的查詢參數(shù)后臺(tái)也是要進(jìn)行校驗(yàn)的,同時(shí)還需要給出校驗(yàn)的返回信息放到上文我們統(tǒng)一封裝的結(jié)構(gòu)中
    2022-07-07
  • JAVA操作HDFS案例的簡(jiǎn)單實(shí)現(xiàn)

    JAVA操作HDFS案例的簡(jiǎn)單實(shí)現(xiàn)

    本篇文章主要介紹了JAVA操作HDFS案例的簡(jiǎn)單實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-08-08

最新評(píng)論

灵台县| 台北县| 津市市| 巴里| 三原县| 兖州市| 莱西市| 柳河县| 沐川县| 乾安县| 秭归县| 讷河市| 克拉玛依市| 黄冈市| 克什克腾旗| 裕民县| 庆云县| 广元市| 芮城县| 崇左市| 讷河市| 南宫市| 武夷山市| 康平县| 洞口县| 仲巴县| 凤山市| 凌海市| 孟连| 石泉县| 文成县| 丹东市| 扶风县| 丘北县| 英吉沙县| 邯郸县| 桦南县| 壶关县| 阿合奇县| 马关县| 沙河市|