關(guān)于ArrayList的ConcurrentModificationException的一些思考

關(guān)于ArrayList的ConcurrentModificationException的一些思考

先來(lái)看一個(gè)來(lái)自于阿里java規(guī)范文檔的例子:

        List<String> a = new ArrayList<String>();
        a.add("1");
        a.add("2");
        for (String temp : a) {
            if("1".equals(temp)){
                a.remove(temp);
            }
        }

這里的執(zhí)行結(jié)果比較奇怪。(待會(huì)再分析)


分析ArrayList的源碼逼纸,可以看到其(或者父類(lèi)AbstractList)維護(hù)一個(gè)modCount的變量:

protected transient int modCount = 0;

根據(jù)文檔音同,這個(gè)變量的作用是

The number of times this list has been <i>structurally modified</i>.
Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.
這個(gè)數(shù)字的描述的是list的結(jié)構(gòu)性修改次數(shù)踪宠,結(jié)構(gòu)性修改指的是鹃共,那些會(huì)改變list的長(zhǎng)度或者破壞list的方式腺逛,使得正在進(jìn)行的迭代產(chǎn)生錯(cuò)誤結(jié)果唤冈。

我們來(lái)看看ArrayList中為什么會(huì)拋出ConcurrentModificationException

  • I remove()方法(改變modCount值)
    public E remove(int index) {
        rangeCheck(index);

        modCount++;//這里
        E oldValue = elementData(index);

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

        return oldValue;
    }

以及add()方法

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

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//look這里

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

可以看出帅容,remove()的時(shí)候是對(duì)modCount進(jìn)行過(guò)修改颇象,同樣的在add()方法中也是,即添加刪除多少次modCount就是多少并徘。我們并沒(méi)有看到ConcurrentModificationException的拋出遣钳,說(shuō)明,無(wú)論我們?cè)趺磳?duì)ArrayList進(jìn)行修改麦乞,只會(huì)改變modCount值而不會(huì)拋出異常蕴茴。
那么是哪里拋出的呢,看下面:

  • II 迭代器Itr(拋出ConcurrentModificationException異常)
 private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;//獲取迭代器的時(shí)候會(huì)初始化expectedModCount

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

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();//檢查list是否被操作過(guò)姐直,若沒(méi)有繼續(xù)
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                //數(shù)據(jù)沒(méi)有被操作而合法的游標(biāo)cursor又大于數(shù)組的長(zhǎng)度倦淀,日了狗了,hasNext()為什么不用return cursor < size
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        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();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

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

從上面的代碼可以看出在迭代器Itr的next()简肴, remove()這兩個(gè)方法在進(jìn)行操作前都會(huì)去使用checkForComodification校驗(yàn)modCount是否一致晃听,即保證當(dāng)前操作前的list沒(méi)有被改變,就像每次出門(mén)進(jìn)門(mén)都會(huì)檢查鎖壞沒(méi)有一樣砰识,若沒(méi)有壞(modCount == expectedModCount)就繼續(xù)開(kāi)門(mén)鎖門(mén)能扒,若壞了(modCount != expectedModCount),就報(bào)警或者找修鎖的(拋出ConcurrentModificationException)辫狼。

所以在多線程環(huán)境中初斑,一個(gè)線程要添加刪除,另一個(gè)線程迭代膨处,很容易中招见秤。


回到最開(kāi)始的例子,這個(gè)例子可以正常的執(zhí)行下去真椿,最后a里只剩一個(gè)元素’2’鹃答。但是啊但是,若改成

...
if(“2”.equals(temp)) {
   //......
}
...

此時(shí)運(yùn)行會(huì)拋出ConcurrentModificationException突硝,這跟我們剛剛分析的不一樣啊测摔,明明沒(méi)有用迭代器,怎么會(huì)拋出ConcurrentModificationException解恰。
沒(méi)辦法锋八,看看異常信息吧,如下:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at cc.rinoux.instantTest.main(instantTest.java:16)
......

果然還是迭代器產(chǎn)生的異常护盈。什么時(shí)候調(diào)的迭代器挟纱?當(dāng)然是foreach語(yǔ)句,foreach語(yǔ)句實(shí)際上是對(duì)iterator.hasNext()和next()的調(diào)用腐宋,先判斷hasNext()紊服,為true再next()獲得元素(并非語(yǔ)法糖檀轨,涉及到Collection和Iterable,此處不贅述)围苫。

但是為什么if(“1”.equals(temp))沒(méi)有拋異常裤园,而if(“2”.equals(temp))拋異常,這就涉及到上面注釋里寫(xiě)的hasNext()為什么要這樣寫(xiě)剂府。

前者在完成第一次next操作后拧揽,cursor變?yōu)?,在remove腺占,size變?yōu)?淤袜;繼續(xù)遍歷,先做hasNext判斷衰伯,此時(shí)size為1 == cursor铡羡,直接返回false跳出循環(huán)遍歷中止,因此沒(méi)機(jī)會(huì)去拋出 ConcurrentModificationException意鲸。

后者則是烦周,第一個(gè)遍歷結(jié)果,不滿足remove條件怎顾,因此modCount依然為0读慎,hasNext()依然為true;第二個(gè)遍歷結(jié)果滿足槐雾,remove夭委,modCount為1, size為1募强, cursor為2株灸;在下一次foreach中,hasNext()通過(guò)擎值,但是next()就不行了慌烧,因?yàn)閙odCount != expectedModCount,所以操作前檢查拋出ConcurrentModificationException鸠儿。
注意這里杏死,cursor是大于size的,游標(biāo)大于list長(zhǎng)度的情況就發(fā)生了捆交,所以hasNext()用的return cursor != size 而非 return cursor < size。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末腐巢,一起剝皮案震驚了整個(gè)濱河市品追,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冯丙,老刑警劉巖肉瓦,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遭京,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡泞莉,警方通過(guò)查閱死者的電腦和手機(jī)哪雕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鲫趁,“玉大人斯嚎,你說(shuō)我怎么就攤上這事“ず瘢” “怎么了堡僻?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)疫剃。 經(jīng)常有香客問(wèn)我钉疫,道長(zhǎng),這世上最難降的妖魔是什么巢价? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任牲阁,我火速辦了婚禮,結(jié)果婚禮上壤躲,老公的妹妹穿的比我還像新娘城菊。我一直安慰自己,他們只是感情好柒爵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布役电。 她就那樣靜靜地躺著,像睡著了一般棉胀。 火紅的嫁衣襯著肌膚如雪法瑟。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天唁奢,我揣著相機(jī)與錄音霎挟,去河邊找鬼。 笑死麻掸,一個(gè)胖子當(dāng)著我的面吹牛酥夭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脊奋,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼熬北,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了诚隙?” 一聲冷哼從身側(cè)響起讶隐,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎久又,沒(méi)想到半個(gè)月后巫延,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體效五,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年炉峰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了畏妖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疼阔,死狀恐怖戒劫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情竿开,我是刑警寧澤谱仪,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站否彩,受9級(jí)特大地震影響疯攒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜列荔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一敬尺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贴浙,春花似錦砂吞、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至袁串,卻和暖如春概而,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背囱修。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工赎瑰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人破镰。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓餐曼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鲜漩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子源譬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容