Java並發編程系列之二十六:ConcurrentModificationException__演算法

來源:互聯網
上載者:User

在多線程程式的編寫中,經常能遇到ConcurrentModificationException這個異常,這個異常的意思是指出現了並發修改的情況,為了降低對程式的效能的影響,Java開發人員將捕獲的這種錯誤以“善意”的方式進行提醒。這種異常一般在對容器的元素遍曆的過程中出現了對容器的寫操作(包括增加、修改和刪除操作)時出現。仔細閱讀源碼就知道,使用迭代器遍曆元素時由一個計數器,這個計數器就是為“快速失敗”機制設計的。

那麼,問題來了,“快速失敗”(fail-fast)又是什麼鬼。就是在迭代過程中對出現並發修改的情況時拋出異常。這個異常就是ConcurrentModificationException異常。既然會出現這種異常自然得想辦法解決這種異常。因為是並發修改導致出現的這種異常,並發修改的一個最大的問題就是沒有使用同步,試想如果對需要遍曆的容器進行加鎖,那麼並發執行的線程將只有一個線程能獲得鎖,這樣就避免了並發修改出現的“快速失敗”。但是這麼解決仍然不夠完美,因為在容器規模比較大或者在單個元素執行的時間過長的時候,那麼這些線程將長時間等待。那麼,線上程競爭激烈的時候很有可能出現死結,即使不出現死結或者饑餓等情況,長時間的加鎖對系統的效能造成很大影響。在類似淘寶這樣的高並發的網站這樣設計將是一個災難。

OK,本著talk is important,show me the code as well(樓主認為溝通也是一項很重要的技能,故不是很認可之前的talk is cheap,show me the code,因為talk並不cheap)的原則,先來看看ConcurrentModificationException異常發生的一個例子:

package com.rhwayfun.patchwork.concurrency.r0408;import java.util.HashSet;import java.util.Random;import java.util.Set;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * Created by rhwayfun on 16-4-8. */public class HiddenUnSafeIterator {    private final Set<Integer> set = new HashSet<>();    public synchronized void add(Integer i){set.add(i);}    public synchronized void remove(Integer i){set.remove(i);}    public void addTenThings(){        Random random = new Random();        for (int i = 0; i < 10; i++)            add(random.nextInt(100));        System.out.println("DEBUG: added ten elements to " + set);    }    public static void main(String[] args){        final HiddenUnSafeIterator hi = new HiddenUnSafeIterator();        ExecutorService threadPool = Executors.newFixedThreadPool(5);        for (int i = 0; i < 15; i++){            threadPool.execute(new Runnable() {                @Override                public void run() {                    hi.addTenThings();                }            });        }    }}

運行該程式沒果然不出意料出現了ConcurrentModificationException異常。你會發現在add(Integer i)方法上添加了synchronized進行同步啊,結果應當不會出現這個異常啊,對不對。從拋出的異常知道這樣分析是錯誤的,主要在於執行System.out.println("DEBUG: added ten elements to " + set);這個操作的時候,實際上在輸出set時,調用了AbstractCollection的toString方法,對容器的元素進行迭代,但是最本質的原因在於迭代的過程中沒有擷取HiddenUnSafeIterator的鎖,因為加在add方法上的synchronized擷取的鎖就是HiddenUnSafeIterator對象的對象層級鎖。這樣,雖然表面上是同步的,但是在迭代過程並沒有實際獲得鎖。這就是這個程式拋出ConcurrentModificationException異常的本質原因。

現在,已經知道上面的程式拋出ConcurrentModificationException異常的本質原因是沒有獲得HiddenUnSafeIterator的對象鎖。那麼只要設計程式,使得對Set容器進行迭代時能夠擷取對象鎖就可以了,在jdk中,預設為我們提供了具有同步功能的synchronizedSet方法。下面就是實現一個安全版本的迭代器,代碼修改如下:

package com.rhwayfun.patchwork.concurrency.r0408;import java.util.Collections;import java.util.HashSet;import java.util.Random;import java.util.Set;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * Created by rhwayfun on 16-4-8. */public class SafeInterator {    //修改的地方在這裡    private final Set<Integer> set = Collections.synchronizedSet(new HashSet<Integer>());    //取消了synchronized    public void add(Integer i){set.add(i);}    public void remove(Integer i){set.remove(i);}    public void addTenThings(){        Random random = new Random();        for (int i = 0; i < 10; i++)            add(random.nextInt(100));        System.out.println("DEBUG: added ten elements to " + set);    }    public static void main(String[] args){        final SafeInterator hi = new SafeInterator();        ExecutorService threadPool = Executors.newFixedThreadPool(5);        for (int i = 0; i < 25; i++){            threadPool.execute(new Runnable() {                @Override                public void run() {                    hi.addTenThings();                }            });        }    }}

運行該程式沒有出現ConcurrentModificationException異常,說明我們之前對異常的分析是正確的。這段代碼解決ConcurrentModificationException異常的主要原因就在於在迭代容器的時候獲得了SafeInterator的對象鎖。

上面的程式雖然使用了同步解決了ConcurrentModificationException異常問題,但是試想在讀遠大於寫的情境下,對每個都需要加鎖是不是很影響效能,有沒有可以不用加鎖的方案呢。答案是就是“複製”。將容器的副本封閉線上程內部,這樣在迭代時就不用考慮並發修改的問題,因為單線程不存在這個問題。具體內容將在我的另一篇文章中闡述。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.