欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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)聽消息進(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)鍵代碼,不知道你有沒有發(fā)現(xiàn)問題?可以先拋開設(shè)計(jì)和代碼實(shí)現(xiàn)方面的問題不談,只看這樣的代碼能不能正常執(zhí)行,得到正確的業(yè)務(wù)結(jié)果。

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

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ù)不一致問題。

再可以看下源碼中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 線程安全問題

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

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

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

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

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

三、改進(jìn)思考

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

經(jīng)過(guò)上述分析,已經(jīng)清楚問題的產(chǎn)生原因了。對(duì)于本問題的優(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類似問題的發(fā)現(xiàn)和改進(jìn)

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

  • 開發(fā)

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

  • 代碼評(píng)審

開發(fā)完成的代碼一定需要進(jìn)行代碼評(píng)審,評(píng)審過(guò)程中架構(gòu)師需要發(fā)揮自己豐富的開發(fā)經(jīng)驗(yàn)和較強(qiáng)的代碼直覺,“火眼金睛”,發(fā)現(xiàn)代碼中的漏洞;當(dāng)然這對(duì)評(píng)審人員的要求很高,因?yàn)閮H通過(guò)改動(dòng)的幾行代碼發(fā)現(xiàn)問題確實(shí)是一件很有挑戰(zhàn)的事情。如果要有一些自動(dòng)化工具或者插件,則可以起到事半功倍的效果。這里其實(shí)我還沒有調(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)模塊之間的交互問題。除了繼承測(cè)試,有時(shí)還需要性能測(cè)試,性能測(cè)試可以發(fā)現(xiàn)潛在的競(jìng)爭(zhēng)條件、死鎖、資源爭(zhēng)用等多線程問題。

四、小結(jié)

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

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

相關(guān)文章

  • Java獲取當(dāng)前時(shí)間并轉(zhuǎn)化為yyyy-MM-dd?HH:mm:ss格式的多種方式

    Java獲取當(dāng)前時(shí)間并轉(zhuǎn)化為yyyy-MM-dd?HH:mm:ss格式的多種方式

    這篇文章主要介紹了Java獲取當(dāng)前時(shí)間并轉(zhuǎn)化為yyyy-MM-dd?HH:mm:ss格式的多種方式,每種方式結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-03-03
  • 如何解決Mybatis-plus中@TableLogic注解失效問題

    如何解決Mybatis-plus中@TableLogic注解失效問題

    這篇文章主要介紹了如何解決Mybatis-plus中@TableLogic注解失效問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Eclipse+Java+Swing+Mysql實(shí)現(xiàn)工資管理系統(tǒng)

    Eclipse+Java+Swing+Mysql實(shí)現(xiàn)工資管理系統(tǒng)

    這篇文章主要介紹了Eclipse+Java+Swing+Mysql實(shí)現(xiàn)工資管理系統(tǒng),對(duì)正在工作或者學(xué)習(xí)的你有一定的參考價(jià)值,需要的朋友可以參考一下
    2022-01-01
  • java 線程創(chuàng)建多線程詳解

    java 線程創(chuàng)建多線程詳解

    本文主要講解java 線程創(chuàng)建多線程的知識(shí),這里對(duì)java線程的創(chuàng)建做了詳細(xì)介紹,并附簡(jiǎn)單示例代碼,有興趣的小伙伴可以參考下
    2016-09-09
  • 詳解Spring事務(wù)和事務(wù)傳播機(jī)制

    詳解Spring事務(wù)和事務(wù)傳播機(jī)制

    本文主要介紹了MySQL中的事務(wù)以及如何在Spring框架中實(shí)現(xiàn)事務(wù)管理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-12-12
  • 詳解Spring Cloud 跨服務(wù)數(shù)據(jù)聚合框架

    詳解Spring Cloud 跨服務(wù)數(shù)據(jù)聚合框架

    這篇文章主要介紹了詳解Spring Cloud 跨服務(wù)數(shù)據(jù)聚合框架,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • JAVA圖片水印開發(fā)案例詳解

    JAVA圖片水印開發(fā)案例詳解

    水印開發(fā)是web開發(fā)中一種比較常見的功能,實(shí)現(xiàn)的代碼很簡(jiǎn)單,這篇文章主要介紹了JAVA圖片水印開發(fā)案例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • Java高級(jí)特性

    Java高級(jí)特性

    這篇文章主要介紹了Java高級(jí)特性,需要的朋友可以參考下
    2017-04-04
  • 深入淺出MappedByteBuffer(推薦)

    深入淺出MappedByteBuffer(推薦)

    MappedByteBuffer使用虛擬內(nèi)存,因此分配(map)的內(nèi)存大小不受JVM的-Xmx參數(shù)限制,但是也是有大小限制的,這篇文章主要介紹了MappedByteBuffer的基本知識(shí),需要的朋友可以參考下
    2022-12-12
  • Sharding-Proxy基本功能用法介紹

    Sharding-Proxy基本功能用法介紹

    這篇文章介紹了Sharding-Proxy基本功能用法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-02-02

最新評(píng)論