一次線上java.util.ConcurrentModificationException異常排查引發(fā)的思考

異常拋出原因

在使用remove方法對ArrayList進(jìn)行刪除操作時剃法,會拋出此異常。

代碼分析

測試用戶類:

package sort;

public class User implements Comparable<User>{

    private int id;

    private String name;

    private int birthDay;

    public User(int id, String name, int birthDay) {
        this.id = id;
        this.name = name;
        this.birthDay = birthDay;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getBirthDay() {
        return birthDay;
    }

    @Override
    public int compareTo(User user) {
        return user.getId() - this.getId();
    }
}

各種remove方法測試:

package sort;

import com.google.common.collect.Lists;

import java.util.Iterator;
import java.util.List;

public class SortTest1 {

    public static void main(String[] args) {
        foreachRemove();
        iteratorRemove();
        iteratorHasNextCheck();
    }

    private static void iteratorHasNextCheck() {
        List<User> list = initUserList();
        Iterator<User> iterator = list.iterator();
        while(iterator.hasNext()){
            User user = iterator.next();
            if("四".equals(user.getName())){
                list.remove(user);
            }else{
                System.out.println(user.getName());
            }
        }
    }

    private static void iteratorRemove() {
        try {
            List<User> list = initUserList();
            Iterator<User> iterator = list.iterator();
            while (iterator.hasNext()) {
                User user = iterator.next();
                if (user.getBirthDay() < 20040101) {
                    iterator.remove();
                }
            }
            System.out.println(list.size());
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("iteratorRemove failed");
        }
    }

    private static void foreachRemove() {
        try {
            List<User> list = initUserList();
            for (User user : list) {
                if (user.getBirthDay() < 20040101) {
                    list.remove(user);
                }
            }
            System.out.println(list.size());
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("foreachRemove failed");
        }

    }

    private static List<User> initUserList() {
        List<User> list = Lists.newArrayList();
        list.add(new User(1,"一", 20010101));
        list.add(new User(2,"二", 20020202));
        list.add(new User(3,"三", 20030303));
        list.add(new User(4,"四", 20040404));
        list.add(new User(5,"五", 20050505));
        return list;
    }
}

結(jié)果:

java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    at java.util.ArrayList$Itr.next(ArrayList.java:859)
    at sort.SortTest1.foreachRemove(SortTest1.java:49)
    at sort.SortTest1.main(SortTest1.java:11)
foreachRemove failed
2
一
二
三

在分析結(jié)果前律适,先貼出ArrayList的迭代器源碼

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;

        Itr() {}

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

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                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();
        }
    }

分析:
1.可以看到hasNext()方法主要是比對當(dāng)前元素的數(shù)組下標(biāo)與迭代器元素的個數(shù)如捅。
2.next方法要先檢查ArrayList的操作數(shù)(modCount虎囚,ArrayList父類AbstractList的成員變量,
這個成員變量記錄著集合的修改次數(shù)五慈,也就每次add或者remove它的值都會加1)有沒有改變纳寂,如果改變就會拋出ConcurrentModificationException異常。

  1. foreachRemove里使用了ArrayList的remove方法泻拦,modCount發(fā)生了變化毙芜,所以拋出異常。
  2. iteratorRemove里使用了ArrayList內(nèi)部類Itr的remove方法争拐,modCount未發(fā)生變化腋粥,所以正常執(zhí)行。
  3. iteratorHasNextCheck里正好移除了第4個元素架曹,此時雖然modCount變化了隘冲,但是元素總數(shù)與索引下標(biāo)都是4,此時hasNext()返回false绑雄,所以不會往下執(zhí)行展辞。
  4. iteratorHasNextCheck里如果移除前3個元素,依舊會拋出ConcurrentModificationException異常绳慎。

線上問題

線上使用guava本地緩存纵竖,并且每次拿出數(shù)據(jù)會再次進(jìn)行Collections.sort()排序,導(dǎo)致拋出此異常杏愤。
偽代碼
:

package sort;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class SortTest2 {

    private static Map<String, List<User>> map = Maps.newHashMap();

    public static void main(String[] args) {
        buildMap();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
                0L, TimeUnit.MILLISECONDS,  new LinkedBlockingQueue<Runnable>());
        for (int i=0; i<5; i++) {
            executor.execute(() -> {
                try {
                    Collections.sort(map.get("list"));
                    Thread.sleep(1000L);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("sort thread error");
                }
            });
        }
        executor.shutdown();
    }

    private static void buildMap() {
        map.put("list", initUserList());
    }

    private static List<User> initUserList() {
        List<User> list = Lists.newArrayList();
        list.add(new User(3,"三", 20030303));
        list.add(new User(2,"二", 20020202));
        list.add(new User(4,"四", 20040404));
        list.add(new User(1,"一", 20010101));
        list.add(new User(5,"五", 20050505));
        return list;
    }
}

結(jié)果(報錯數(shù)量不定靡砌,取決于并發(fā)數(shù)與排序執(zhí)行時間):

java.util.ConcurrentModificationException
    at java.util.ArrayList.sort(ArrayList.java:1464)
    at java.util.Collections.sort(Collections.java:141)
    at sort.SortTest2.lambda$main$0(SortTest2.java:24)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
sort thread error
java.util.ConcurrentModificationException
    at java.util.ArrayList.sort(ArrayList.java:1464)
    at java.util.Collections.sort(Collections.java:141)
    at sort.SortTest2.lambda$main$0(SortTest2.java:24)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
sort thread error
java.util.ConcurrentModificationException
    at java.util.ArrayList.sort(ArrayList.java:1464)
    at java.util.Collections.sort(Collections.java:141)
    at sort.SortTest2.lambda$main$0(SortTest2.java:24)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
sort thread error

ArrayList中的sort()方法

@Override
    @SuppressWarnings("unchecked")
    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

主要調(diào)用了Arrays的sort()方法,此排序方法本文暫不介紹,后續(xù)modCount進(jìn)行了自增珊楼,此處在多個線程一起執(zhí)行下會出現(xiàn)問題通殃,拋出ConcurrentModificationException異常。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市画舌,隨后出現(xiàn)的幾起案子堕担,更是在濱河造成了極大的恐慌,老刑警劉巖曲聂,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件霹购,死亡現(xiàn)場離奇詭異,居然都是意外死亡朋腋,警方通過查閱死者的電腦和手機(jī)齐疙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旭咽,“玉大人贞奋,你說我怎么就攤上這事∏蠲啵” “怎么了轿塔?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長仲墨。 經(jīng)常有香客問我勾缭,道長,這世上最難降的妖魔是什么宗收? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任漫拭,我火速辦了婚禮,結(jié)果婚禮上混稽,老公的妹妹穿的比我還像新娘采驻。我一直安慰自己,他們只是感情好匈勋,可當(dāng)我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布礼旅。 她就那樣靜靜地躺著,像睡著了一般洽洁。 火紅的嫁衣襯著肌膚如雪痘系。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天饿自,我揣著相機(jī)與錄音汰翠,去河邊找鬼。 笑死昭雌,一個胖子當(dāng)著我的面吹牛复唤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播烛卧,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼佛纫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起呈宇,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤好爬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后甥啄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體存炮,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年型豁,在試婚紗的時候發(fā)現(xiàn)自己被綠了僵蛛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尚蝌。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡迎变,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出飘言,到底是詐尸還是另有隱情衣形,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布姿鸿,位于F島的核電站谆吴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏苛预。R本人自食惡果不足惜句狼,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望热某。 院中可真熱鬧腻菇,春花似錦、人聲如沸昔馋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秘遏。三九已至丘薛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間邦危,已是汗流浹背洋侨。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留倦蚪,地道東北人希坚。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像审丘,于是被迫代替她去往敵國和親吏够。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,494評論 2 348

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