成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

【拾遺補(bǔ)缺】java ArrayList的不當(dāng)使用導(dǎo)致的ConcurrentModification

huhud / 2578人閱讀

摘要:中有三個(gè)迭代器相關(guān)的函數(shù),返回兩種迭代器實(shí)現(xiàn),分別是和。根據(jù)堆棧信息找到出錯(cuò)的地方可以看到,保證其遍歷時(shí)不被修改,采用的是用一個(gè)計(jì)數(shù)器的機(jī)制。

今天組內(nèi)的一個(gè)同學(xué)碰到一個(gè)并發(fā)問(wèn)題,幫忙看了下。是個(gè)比較小的點(diǎn),但由于之前沒(méi)碰到過(guò)所以也沒(méi)特意了解過(guò)這塊,今天既然看了就沉淀下來(lái)。

原始問(wèn)題是看到日志里有一些零星的異常,如下如所示

根據(jù)堆棧信息,可以很快定位到對(duì)應(yīng)的應(yīng)用代碼,同時(shí)根據(jù)異常的描述,可以初步定為是并發(fā)訪問(wèn)ArrayList造成的。

相關(guān)應(yīng)用代碼如下(也就是堆棧第三行的CommonUtil.getItemFromList)

這里的list是由上層邏輯傳入的

提到Collection的遍歷,第一時(shí)間想到兩種可能性(非針對(duì)java,只是一般性的想法):

迭代器內(nèi)部會(huì)保存當(dāng)前的遍歷位置,那么多個(gè)線程同時(shí)遍歷時(shí)遍歷位置屬于共享變量,會(huì)導(dǎo)致多線程問(wèn)題

在一個(gè)線程遍歷過(guò)程中,List被其他線程修改,導(dǎo)致List長(zhǎng)度產(chǎn)生變化

多線程遍歷安全

對(duì)于以上兩個(gè)可能性,其實(shí)只要稍加思考,就能想到第一個(gè)可能性是不太可能的,因?yàn)槭莏ava基本要保證的。通過(guò)查看ArrayList的源碼也基本確定了這個(gè)點(diǎn)。

ArrayList中有三個(gè)迭代器相關(guān)的函數(shù),返回兩種迭代器實(shí)現(xiàn),分別是ListIterator和Iterator。看名字就知道前者只能用于List的遍歷,后者可用于所有Collection的遍歷,對(duì)于for循環(huán)來(lái)說(shuō),使用的是后者。這點(diǎn)參考這兩個(gè)頁(yè)面。

http://beginnersbook.com/2014...

https://stackoverflow.com/que...

Iterator相關(guān)代碼如下

從這里就可以看出來(lái),多線程遍歷同一個(gè)List是安全的。因?yàn)榈魇窃诿看蝔or循環(huán)(調(diào)用iterator)時(shí)生成的實(shí)例,每次實(shí)例獨(dú)立保存當(dāng)前的遍歷進(jìn)度(圖中的cursor字段),這樣每個(gè)線程在遍歷時(shí)只會(huì)修改自己線程所創(chuàng)建的Itr對(duì)象,沒(méi)有共享變量被修改。

遍歷中修改不安全

排除了上面這種可能性,問(wèn)題因?yàn)榛揪投ㄎ涣恕?/p>

根據(jù)堆棧信息找到出錯(cuò)的地方

可以看到,List保證其遍歷時(shí)不被修改,采用的是用一個(gè)計(jì)數(shù)器的機(jī)制。

在開(kāi)始遍歷前,先記錄當(dāng)前的modCount值

而后每次訪問(wèn)下一個(gè)元素之前,都會(huì)檢查下modCount值是否變化,如果有變化,說(shuō)明List的長(zhǎng)度有變化。一旦長(zhǎng)度有變化,就會(huì)拋出ConcurrentModificationException異常。

modCount的注釋詳細(xì)說(shuō)明了這個(gè)字段表明List發(fā)生結(jié)構(gòu)性變化(長(zhǎng)度被修改)的次數(shù),也就是刪除插入等操作時(shí),這個(gè)字段要加一。有興趣的讀者可以自行搜索下ArrayList代碼,看看哪些操作會(huì)引起modCount的變化。

定位罪魁禍?zhǔn)?/b>

明確了原因,找具體代碼問(wèn)題的時(shí)候反而有些波折。因?yàn)閺拇a看這個(gè)循環(huán)并沒(méi)有什么特別,同事一直說(shuō)是和反射有關(guān)(反射內(nèi)部有時(shí)候會(huì)對(duì)類的某些字段的可訪問(wèn)標(biāo)進(jìn)行修改),但我自己跟了代碼并沒(méi)有發(fā)現(xiàn)什么可疑的地方,無(wú)奈寫(xiě)了個(gè)小demo驗(yàn)證下。

public class MultiThreadArrayListThread {

    public static List list = new ArrayList();
    public static Random random = new Random(System.currentTimeMillis());

    public static class TestBean {
        private Integer value;

        public Integer getValue() {
            return value;
        }

        public void setValue(Integer value) {
            this.value = value;
        }
    }

    public static class TestThread extends Thread {

        @Override
        public void run() {
            for (Object o : list) {
                /*if (Thread.currentThread().getName().equals("1")) {
                    list.add(new TestBean());
                }*/
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + org.apache.commons.beanutils.BeanUtils.getProperty(o, "value"));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(random.nextInt(100));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        int i = 0;
        while (i < 100) {
            TestBean testBean = new TestBean();
            testBean.setValue(i);
            list.add(testBean);
            i++;
        }

        int thread = 0;
        while (thread < 20) {
            TestThread testThread = new TestThread();
            testThread.setName(String.valueOf(thread));
            testThread.start();
            thread++;
        }
    }
}

上述代碼執(zhí)行后并沒(méi)有報(bào)錯(cuò),只有在注釋掉的add操作打開(kāi)后,才會(huì)拋異常。

這個(gè)demo進(jìn)一步驗(yàn)證了自己對(duì)于異常原因的認(rèn)知,同時(shí)也說(shuō)明了反射的確不會(huì)影響List的遍歷。因此我的注意力從這段代碼中移開(kāi),轉(zhuǎn)而關(guān)注List的獲取。

這下發(fā)現(xiàn)問(wèn)題所在了。

這里同事犯了個(gè)低級(jí)錯(cuò)誤。這段代碼的邏輯是有ABCD四個(gè)配置信息,要返回這四個(gè)配置信息的并集。但同事的代碼直接在第一個(gè)List中添加后幾個(gè)List的元素了。由于引用是同一個(gè),因此出現(xiàn)了線程a在執(zhí)行完這段邏輯拿到一個(gè)List(其中包含A+B+C+D)并開(kāi)始遍歷時(shí),線程b開(kāi)始執(zhí)行這段邏輯。此時(shí)線程a和線程b拿到的其實(shí)是同一個(gè)List引用(最開(kāi)始的A),并且在線程a遍歷時(shí)線程b對(duì)其進(jìn)行了修改(add(B/C/D)),因此會(huì)觸發(fā)線程a拋異常。不僅如此,哪怕不拋異常,每次業(yè)務(wù)要去拿這個(gè)配置文件,都會(huì)在該集合中加入BCD的元素,集合元素會(huì)遞增(A -> ABCD -> ABCDBCD -> ABCDBCDBCD …),一直運(yùn)行會(huì)導(dǎo)致OOM!

定位到問(wèn)題后修復(fù)就很簡(jiǎn)單了,每次獲取配置時(shí)new一個(gè)新的List即可。

ArrayList list = new ArrayList();
list.add(A);
list.add(B);
list.add(C);
list.add(D);

至此問(wèn)題順利結(jié)局~

小結(jié)

這個(gè)問(wèn)題最終定位到是一個(gè)低級(jí)的代碼錯(cuò)誤,但過(guò)程還是值得記錄下的。自己雖在java這方面工作數(shù)年,但像modCount這種機(jī)制,要是沒(méi)有遇到特定的問(wèn)題還是沒(méi)可能面面俱到每個(gè)小點(diǎn)都關(guān)注到的。今天碰到的這個(gè)小case正好幫助自己拾遺補(bǔ)缺,相信以后碰到ArrayList相關(guān)的問(wèn)題,會(huì)更容易解決~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76299.html

相關(guān)文章

  • 拾遺補(bǔ)缺java ArrayList不當(dāng)使用導(dǎo)致ConcurrentModification

    摘要:中有三個(gè)迭代器相關(guān)的函數(shù),返回兩種迭代器實(shí)現(xiàn),分別是和。根據(jù)堆棧信息找到出錯(cuò)的地方可以看到,保證其遍歷時(shí)不被修改,采用的是用一個(gè)計(jì)數(shù)器的機(jī)制。 今天組內(nèi)的一個(gè)同學(xué)碰到一個(gè)并發(fā)問(wèn)題,幫忙看了下。是個(gè)比較小的點(diǎn),但由于之前沒(méi)碰到過(guò)所以也沒(méi)特意了解過(guò)這塊,今天既然看了就沉淀下來(lái)。 原始問(wèn)題是看到日志里有一些零星的異常,如下如所示 showImg(https://segmentfault.co...

    13651657101 評(píng)論0 收藏0
  • 拾遺補(bǔ)缺java ArrayList不當(dāng)使用導(dǎo)致ConcurrentModification

    摘要:中有三個(gè)迭代器相關(guān)的函數(shù),返回兩種迭代器實(shí)現(xiàn),分別是和。根據(jù)堆棧信息找到出錯(cuò)的地方可以看到,保證其遍歷時(shí)不被修改,采用的是用一個(gè)計(jì)數(shù)器的機(jī)制。 今天組內(nèi)的一個(gè)同學(xué)碰到一個(gè)并發(fā)問(wèn)題,幫忙看了下。是個(gè)比較小的點(diǎn),但由于之前沒(méi)碰到過(guò)所以也沒(méi)特意了解過(guò)這塊,今天既然看了就沉淀下來(lái)。 原始問(wèn)題是看到日志里有一些零星的異常,如下如所示 showImg(https://segmentfault.co...

    曹金海 評(píng)論0 收藏0
  • LEETCODE刷題記錄【27 Remove Element】

    摘要:復(fù)雜度分析時(shí)間復(fù)雜度遍歷次空間復(fù)雜度還有沒(méi)有優(yōu)化空間方法在某些特定場(chǎng)景下會(huì)進(jìn)行不必要的復(fù)制操作,影響性能。注意尾部的元素有可能是需要剔除的,所以,下一輪循環(huán)要從當(dāng)前索引重新開(kāi)始。 給定一個(gè)數(shù)組 nums?和一個(gè)值 val,你需要原地移除所有數(shù)值等于?val?的元素,返回移除后數(shù)組的新長(zhǎng)度。不要使用額外的數(shù)組空間,你必須在原地修改輸入數(shù)組并在使用 O(1) 額外空間的條件下完成。 元素的...

    馬龍駒 評(píng)論0 收藏0
  • Java并發(fā)編程——線程基礎(chǔ)查漏補(bǔ)缺

    摘要:告訴當(dāng)前執(zhí)行的線程為線程池中其他具有相同優(yōu)先級(jí)的線程提供機(jī)會(huì)。不能保證會(huì)立即使當(dāng)前正在執(zhí)行的線程處于可運(yùn)行狀態(tài)。當(dāng)達(dá)到超時(shí)時(shí)間時(shí),主線程和是同樣可能的執(zhí)行者候選。下一篇并發(fā)編程線程安全性深層原因 Thread 使用Java的同學(xué)對(duì)Thread應(yīng)該不陌生了,線程的創(chuàng)建和啟動(dòng)等這里就不講了,這篇主要講幾個(gè)容易被忽視的方法以及線程狀態(tài)遷移。 wait/notify/notifyAll 首先我...

    luqiuwen 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<