記錄一次ConcurrentModificationException排查

問題背景:

android項(xiàng)目在迭代的過程中,有個獲取定位的功能模塊拦英,一直有個異常居高不下蜒什。
嘗試修改了好幾版,都沒有解決掉這個問題
日志如下:

01-04 19:55:57.770 24194 24194 E AndroidRuntime: FATAL EXCEPTION: main
1501-04 19:55:57.770 24194 24194 E AndroidRuntime: Process: com.**, PID: 24194
1601-04 19:55:57.770 24194 24194 E AndroidRuntime: java.util.ConcurrentModificationException
1701-04 19:55:57.770 24194 24194 E AndroidRuntime: at java.util.ArrayList$Itr.next(ArrayList.java:860)
1801-04 19:55:57.770 24194 24194 E AndroidRuntime: at aiv$b.a(LocationManager.java:124)
1901-04 19:55:57.770 24194 24194 E AndroidRuntime: at com.baidu.location.LocationClient.b(Unknown Source:66)
2001-04 19:55:57.770 24194 24194 E AndroidRuntime: at com.baidu.location.LocationClient.a(Unknown Source:0)
2101-04 19:55:57.770 24194 24194 E AndroidRuntime: at com.baidu.location.LocationClient$a.handleMessage(Unknown Source:171)
2201-04 19:55:57.770 24194 24194 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:107)
2301-04 19:55:57.770 24194 24194 E AndroidRuntime: at android.os.Looper.loop(Looper.java:227)
2401-04 19:55:57.770 24194 24194 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7668)
2501-04 19:55:57.770 24194 24194 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
2601-04 19:55:57.770 24194 24194 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
2701-04 19:55:57.770 24194 24194 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:960)

該錯誤日志其實(shí)很熟悉疤估,就是 fail-fast機(jī)制(java集合(Collection)中的一種錯誤機(jī)制)灾常。

場景復(fù)現(xiàn):

最原始代碼版本:

public final class LocationManager {
    private static final class LocationManagerInstance {
        private static final LocationManager INSTANCE = new LocationManager();
    }

    public static LocationManager getInstance() {
        return LocationManagerInstance.INSTANCE;
    }

    private LocationManager() {
        // do nothing
    }

    private static List<SoftReference<IBdLocationListener>> mWeakReferences = new ArrayList<>();
    //    mILocationListener;
    private static MyLocationListener myListener = new MyLocationListener();

    private LocationClient mLocationClient;

    /**
     * 開始申請 定位
     */
    public void startLocate(IBdLocationListener locationListener, Application application) {
        mLocationClient = new LocationClient(application);
            if (mLocationClient != null) {
                mWeakReferences.add(new SoftReference<>(locationListener));
                if (locationListener != null) {
                    locationListener.startLocation();
                }
                LogUtils.i("start collect location info: thread name:" + Thread.currentThread().getName());
                mLocationClient.start();

            }
    }

    /**
     * 取消注冊
     *
     * @param listener
     */
    public void unRegisterListener(IBdLocationListener listener) {
        synchronized (LocationManager.class) {

            if (mWeakReferences == null || mWeakReferences.isEmpty()) {
                return;
            }

            for (int i = mWeakReferences.size() - 1; i >= 0; i--) {
                SoftReference<IBdLocationListener> itemListener = mWeakReferences.get(i);
                if (itemListener != null && itemListener.get() != null && itemListener.get() == listener) {
                    mWeakReferences.remove(i);
                }
            }

        }

    }

    //BDAbstractLocationListener
    public static class MyLocationListener extends BDAbstractLocationListener {
        @Override
        public void onReceiveLocation(BDLocation location) {
                                if (mWeakReferences != null) {

                                    Iterator<SoftReference<IBdLocationListener>> iterator = mWeakReferences.iterator();
                                    while (iterator.hasNext()) {
                                        SoftReference<IBdLocationListener> next = iterator.next();
                                        if (next == null || next.get() == null) {
                                            iterator.remove();
                                            continue;
                                        }
                                        if (next.get() != null) {
                                            next.get().onReceiveLocation(location);
                                        }
                                    }
                                }
                            }
                    });

        }
    }
}

由于是涉及到定位sdk,猜測是多線程沒加鎖導(dǎo)致的铃拇,數(shù)據(jù)不同步钞瀑。于是直接加鎖嘗試解決上線(本身概率性事件,復(fù)現(xiàn)概率不是很高)慷荔。
修改如下版本:

public final class LocationManager {
    private static final class LocationManagerInstance {
        private static final LocationManager INSTANCE = new LocationManager();
    }

    public static LocationManager getInstance() {
        return LocationManagerInstance.INSTANCE;
    }

    private LocationManager() {
        // do nothing
    }

    private static List<SoftReference<IBdLocationListener>> mWeakReferences = new ArrayList<>();
    //    mILocationListener;
    private static MyLocationListener myListener = new MyLocationListener();

    private LocationClient mLocationClient;

    /**
     * 開始申請 定位
     */
    public void startLocate(IBdLocationListener locationListener, Application application) {
        mLocationClient = new LocationClient(application);
        synchronized (LocationManager.class) {
            if (mLocationClient != null) {
                mWeakReferences.add(new SoftReference<>(locationListener));
                if (locationListener != null) {
                    locationListener.startLocation();
                }
                mLocationClient.start();
            }
        }
    }


    /**
     * 取消注冊
     *
     * @param listener
     */
    public void unRegisterListener(IBdLocationListener listener) {
        synchronized (LocationManager.class) {
            if (mWeakReferences == null || mWeakReferences.isEmpty()) {
                return;
            }
            for (int i = mWeakReferences.size() - 1; i >= 0; i--) {
                SoftReference<IBdLocationListener> itemListener = mWeakReferences.get(i);
                if (itemListener != null && itemListener.get() != null && itemListener.get() == listener) {
                    mWeakReferences.remove(i);
                }
            }

        }

    }

    //BDAbstractLocationListener
    public static class MyLocationListener extends BDAbstractLocationListener {
        @Override
        public void onReceiveLocation(BDLocation location) {
                            synchronized (LocationManager.class) {
                                if (mWeakReferences != null) {
                                    Iterator<SoftReference<IBdLocationListener>> iterator = mWeakReferences.iterator();
                                    while (iterator.hasNext()) {
                                        SoftReference<IBdLocationListener> next = iterator.next();
                                        if (next == null || next.get() == null) {
                                            iterator.remove();
                                            continue;
                                        }
                                        if (next.get() != null) {
                                            next.get().onReceiveLocation(location);
                                        }
                                    }
                                }
                            }
        }
    }
}

一直以為是線程不同步導(dǎo)致的雕什,但是發(fā)現(xiàn)加上鎖之后還是會報這個異常。

思考:

1.難道鎖對象不是同一個显晶?(確認(rèn)過確實(shí)是同一個)
2.同一個線程能同時 執(zhí)行兩處代碼监徘?(差點(diǎn)顛覆了我這么多年 理論的認(rèn)知)

哎,經(jīng)過網(wǎng)上查找資料吧碾。確實(shí)也是和自己理解的一樣:
參考資料:https://www.cnblogs.com/dolphin0520/p/3933551.html

對于該異常的結(jié)論:

1.在同一個線程中 確實(shí)也會報這個錯誤:
測試代碼如下:

public class Test {
    public static void main(String[] args)  {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==2)
                list.remove(integer);
        }
    }
}

2.多線程中更會報這個錯誤:
測試代碼如下:

public class Test {
    private static ArrayList<String> testList = new ArrayList<>();

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {

            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 100000; j++) {
                        testList.add("" + j);

                    }
                }
            }).start();
        }


        for (int i = 0; i < 10; i++) {

            new Thread(new Runnable() {
                @Override
                public void run() {

                    for (int j = 0; j < 10000; j++) {

                        for (String s : testList) {

                            System.out.println(s);
                        }
                    }
                }
            }).start();
        }

    }
}

思考:

通過測試以及 網(wǎng)上查閱資料凰盔,解決方法無非就是兩種(加鎖 或者使用 CopyOnWriteArrayList 線程安全的集合),
那為啥我的代碼倦春,還是報錯呢户敬??睁本?哎尿庐,苦惱....

最終結(jié)論:

經(jīng)過重新進(jìn)行了代碼邏輯走查,發(fā)現(xiàn):在遍歷所有監(jiān)聽器對象并且調(diào)用onReceiveLocation方法的時候呢堰。該對象的回調(diào)中抄瑟,又會調(diào)用unRegisterListener 方法,
查看unRegisterListener方法:發(fā)現(xiàn)該方法中會進(jìn)行 集合的刪除操作枉疼。
看到這皮假,這不就是單線程中 產(chǎn)生該異常的原因嘛撮抓。我擦犬辰。侠畔。毛萌。。尼瑪褪测。猴誊。。

這種迷惑性比較強(qiáng)的代碼侮措,確實(shí)有點(diǎn)坑爹懈叹。哎,學(xué)藝不精啊分扎。澄成。

最終修復(fù)版本代碼:

public final class LocationManager {
    private static final class LocationManagerInstance {
        private static final LocationManager INSTANCE = new LocationManager();
    }

    public static LocationManager getInstance() {
        return LocationManagerInstance.INSTANCE;
    }

    private LocationManager() {
        // do nothing
    }

    private static List<SoftReference<IBdLocationListener>> mWeakReferences = new ArrayList<>();
    //    mILocationListener;
    private static MyLocationListener myListener = new MyLocationListener();

    private LocationClient mLocationClient;

    /**
     * 開始申請 定位
     */
    public void startLocate(IBdLocationListener locationListener, Application application) {
        mLocationClient = new LocationClient(application);
      
        synchronized (LocationManager.class) {
            if (mLocationClient != null) {
                mWeakReferences.add(new SoftReference<>(locationListener));
                if (locationListener != null) {
                    locationListener.startLocation();
                }
                mLocationClient.start();

            }
        }
    }

    /**
     * 取消注冊
     *
     * @param listener
     */
    public void unRegisterListener(IBdLocationListener listener) {
        synchronized (LocationManager.class) {

            if (mWeakReferences == null || mWeakReferences.isEmpty()) {
                return;
            }
            for (int i = mWeakReferences.size() - 1; i >= 0; i--) {
                SoftReference<IBdLocationListener> itemListener = mWeakReferences.get(i);
                if (itemListener != null && itemListener.get() != null && itemListener.get() == listener) {
                    // 在此處不要刪除,重置為null笆包。在調(diào)用處,使用iterator 遍歷的時候略荡,刪除
                    mWeakReferences.set(i, null);
                }
            }

        }

    }

    //BDAbstractLocationListener
    public static class MyLocationListener extends BDAbstractLocationListener {
        @Override
        public void onReceiveLocation(BDLocation location) {
                            synchronized (LocationManager.class) {
                                if (mWeakReferences != null) {

                                    Iterator<SoftReference<IBdLocationListener>> iterator = mWeakReferences.iterator();
                                    while (iterator.hasNext()) {
                                        SoftReference<IBdLocationListener> next = iterator.next();
                                        if (next == null || next.get() == null) {
                                            iterator.remove();
                                            continue;
                                        }
                                        if (next.get() != null) {
                                            next.get().onReceiveLocation(location);
                                        }
                                    }
                                }
                            }
        }
    }
}

結(jié)論雖然是很簡單庵佣,但是排查過程中確實(shí)是很痛苦的。汛兜。巴粪。
做一下開發(fā)過程中的簡單記錄吧,加油V嗝8馗!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末漏策,一起剝皮案震驚了整個濱河市派哲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掺喻,老刑警劉巖芭届,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異感耙,居然都是意外死亡褂乍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門即硼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逃片,“玉大人,你說我怎么就攤上這事只酥∪焓担” “怎么了呀狼?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長性锭。 經(jīng)常有香客問我赠潦,道長,這世上最難降的妖魔是什么草冈? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任她奥,我火速辦了婚禮,結(jié)果婚禮上怎棱,老公的妹妹穿的比我還像新娘哩俭。我一直安慰自己,他們只是感情好拳恋,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布凡资。 她就那樣靜靜地躺著,像睡著了一般谬运。 火紅的嫁衣襯著肌膚如雪隙赁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天梆暖,我揣著相機(jī)與錄音伞访,去河邊找鬼。 笑死轰驳,一個胖子當(dāng)著我的面吹牛厚掷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播级解,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼冒黑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了勤哗?” 一聲冷哼從身側(cè)響起抡爹,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芒划,沒想到半個月后豁延,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腊状,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年诱咏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缴挖。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡袋狞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情苟鸯,我是刑警寧澤同蜻,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站早处,受9級特大地震影響湾蔓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜砌梆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一默责、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咸包,春花似錦桃序、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至坟比,卻和暖如春芦鳍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背葛账。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工柠衅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人注竿。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓茄茁,卻偏偏與公主長得像魂贬,于是被迫代替她去往敵國和親巩割。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

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