Java併發編程:同步類容器的問題

同步容器類存在的問題

同步類容器都是線程安全的,但在某些場景下可能需要加鎖來保護複合操作,在複合操作,如:迭代、跳轉已經條件運算中,這些操作可能會表現出意外的行爲,最經典的便是ConcurrentModificationException,原因是當容器迭代的過程中,被併發的修改了內容,這是由於早起迭代器設計的時候並沒有考慮併發修改的原因。

開門見山,我們直接來看兩個例子:

public class UseVector {
    /**
     * 遍歷向量
     * @return
     */
    public void travelVector(Vector<String> list) {
        for (String str: list) {
            System.out.println(str);
        }
    }

    /**
     * 該方法的主要作用是:從數組中移除指定的元素
     * @param list
     * @param target
     * @return 拋出異常java.util.ConcurrentModificationException
     */
    public Collection<String> removeOne(Vector<String> list, String target) {
        if(target == null || "".equals(target)) {
            return list;
        }

        //拋出java.util.ConcurrentModificationException異常
        for(String str : list) {
            if(target.equals(str)) {
                list.remove(str);
            }
        }

        return list;
    }

    public static void main(String[] args) {
        //定義了一個動態數組,並向動態數組中添加3個元素
        Vector<String> vector = new Vector<String>();
        vector.add("1");
        vector.add("2");
        vector.add("3");

        UseVector demo = new UseVector();
        //遍歷這個數組
        demo.travelVector(vector);
        //從這個動態數組中移除3這個元素
        demo.removeOne(vector, "3");
    }
}

該案例創建了一個動態數組Vector並向動態數組中添加3個元素,同時該類有兩個方法,分別是travelVector()遍歷動態數組方法和removeOne從動態數組中移除掉目標元素(target)的方法。運行上訴代碼,程序拋出異常:

java.util.ConcurrentModificationException
	at java.util.Vector$Itr.checkForComodification(Vector.java:1210)
	at java.util.Vector$Itr.next(Vector.java:1163)
	at com.springchang.threadcore.test.UseVector.removeOne(UseVector.java:31)
	at com.springchang.threadcore.test.UseVector.main(UseVector.java:51)

果不其然,該代碼拋出了在併發編程中常見的ConcurrentModificationException異常,並追蹤上述代碼得知是removeOne方法中的list.remove(str);這一行拋出的異常。咱暫時先不管爲啥拋出的異常,先想想有沒有其他代替方案解決我們的需求:遍歷我們的數組,當數組元素等於目標值時將其移除。上述循環數組用的是增強for循環的方法,我們不妨換成另一種方式,用Iterator迭代器試一試。

public class UseVector {
    /**
     * 通過迭起器方法移除一個元素
     * @param list
     * @param target
     * @return  拋出異常java.util.ConcurrentModificationException
     */
    public Collection<String> removeOneByIt(Vector<String> list, String target) {
        if(target == null || "".equals(target)) {
            return list;
        }

        Iterator<String> it = list.iterator();
        while(it.hasNext()) {
            String str = it.next();
            if(target.equals(str)) {
                list.remove(str);
            }
        }
        return list;
    }

    public static void main(String[] args) {
        //定義了一個動態數組,並向動態數組中添加3個元素
        Vector<String> vector = new Vector<String>();
        vector.add("1");
        vector.add("2");
        vector.add("3");

        UseVector demo = new UseVector();

        //從這個動態數組中移除3這個元素
        demo.removeOneByIt(vector, "3");
    }
}

悲傷的是Iterator迭代器並沒有解決我們的問題,同樣的Iterator也拋出了ConcurrentModificationException異常。在JDK 1.5之前有沒有解決的方案呢?有的,請看以下代碼:

public class UseVector {
    /**
     * 安全的從數組中移除元素的方法
     * @param list
     * @param target
     * @return  不會拋出拋出異常java.util.ConcurrentModificationException
     */
    public Collection<String> removeOneBySafe(Vector<String> list, String target) {
        if(target == null || "".equals(target)) {
            return list;
        }

        //該方法是單現成的,所以安全
        for(int i = 0; i < list.size(); i++) {
            if(target.equals(list.get(i))) {
                list.remove(target);
            }
        }
        return list;
    }


    public static void main(String[] args) {
        //定義了一個動態數組,並向動態數組中添加3個元素
        Vector<String> vector = new Vector<String>();
        vector.add("1");
        vector.add("2");
        vector.add("3");

        UseVector demo = new UseVector();

        //從這個動態數組中移除3這個元素
        demo.removeOneBySafe(vector, "3");

        System.out.println("移除後的數組內容:" + vector);
    }
}

如上所述,上述代碼並沒有使用增強的for循環來遍歷數組,也不用Iterator迭代器來遍歷數組。那麼前兩者與最後一個例子的區別在哪裏呢?這是因爲無論是增強的for循環還是迭代器在運行的時候迭代器多了一個線程來控制遊標位置,控制遊標當前的指向位置,而案例3是單線程的,只有一個i下標指向當前元素位置,讀和寫操作都在同一線程中完成的,故而安全。

同步類容器的使用

同步類容器如Vector,Hashtable等這些容器的同步功能都是有JDK的Collections.synchronized***等工廠方法去創建實現的。其底層機制用synchronized關鍵字對每個公用的方法都進行同步,或者使用Object mutex對象鎖的機制使得每次只能有一個線程訪問容器的狀態。使用的代碼如下:

   public static void main(String[] args) {
        //定義了一個動態數組,並向動態數組中添加3個元素
        Vector<String> vector = new Vector<String>();
        vector.add("1");
        vector.add("2");
        vector.add("3");

        UseVector demo = new UseVector();

        Collection<String> col = Collections.synchronizedCollection(vector); //先將線程不安全的同步類vectory轉爲線程安全的col
        System.out.println(col);
    }

要想把同步類變成線程安全,先使用Collections.synchronized***方法將vectory轉爲線程安全的Colection接口的子類,然後在來操作該方法返回的引用,其實現的原理是在底層給對象加鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章