Javaマルチスレッド下の集合クラス同時修正例外処理

6149 ワード

ArrayListはスレッドが安全ではありません.addメソッドで要素を追加する場合、同時性を保証するためにロックされていないためです.
public class CollectionTest {

    public static void main(String[] args) {
        final List list = new ArrayList<>();
        for (int i = 0; i <20; i++) { // 20 

            new Thread(new Runnable() {
                @Override
                public void run() { //list 
                    list.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(list);
                }
            }).start();
        }
    }
}

-  -- - -- - - - - - - -
java.util.ConcurrentModificationException

 

解決策1:ArrayListの代わりにVectorを使用すると、VectorはArrayListに比べてスレッドが安全であり、Vectorのすべての集合内要素に対する操作方法はsynchronizedによって修飾される.でも!VectorはJDK 1.0は導入され、ArrayListは1.2は導入された.なぜスレッドが安全なのに安全でないものを新しく出すのですか?
Vectorの欠点:synchronizedを加えたため、同時性が失われ、効率が大幅に低下する.
解決策2:Collectionsを用いる.synchronizedList(new ArrayList()は、new ArrayList()の代わりになります.
public class CollectionTest {

    public static void main(String[] args) {
        // final List list = new ArrayList<>();
        // final List list = new Vector<>();
        final List list = Collections.synchronizedList(new ArrayList());
        for (int i = 0; i <20; i++) {

            new Thread(new Runnable() {
                @Override
                public void run() {
                    list.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(list);
                }
            }).start();
        }
    }
}

ソリューション3:ArrayListの代わりにCopyOnWriteArrayList(書き込み時コピー)を使用します.
public class CollectionTest {

    public static void main(String[] args) {
        //final List list = new ArrayList<>();
        //final List list = new Vector<>();
        //final List list = Collections.synchronizedList(new ArrayList());
        final List list = new CopyOnWriteArrayList<>();
        for (int i = 0; i <20; i++) {

            new Thread(new Runnable() {
                @Override
                public void run() {
                    list.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(list);
                }
            }).start();
        }
    }
}

CopyOnWriteArrayListソースコード分析

public class CopyOnWriteArrayList
    implements List, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /**
     * The lock protecting all mutators.  (We have a mild preference
     * for builtin monitors over ReentrantLock when either will do.)
     */
    final transient Object lock = new Object();

    /** The array, accessed only via getArray/setArray. */
    // Android-changed: renamed array -> elements for backwards compatibility b/33916927
    private transient volatile Object[] elements; //volatile , 

...
...

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        }
    }
...
...
  • まず、下位配列はvolatileによって修飾され、その可視性と秩序性を保証し、すなわち、多重同時の場合のデータの一致性を保証する.
  • 次にadd法でデータを追加する際に同期ロックを用い,その原子性を保証した.
  • 最後に、配列に要素を追加する場合、現在の配列に直接追加するのではなく、新しい配列newElements、新しい配列長+1をコピーし、要素を配列の最後のビットに割り当て、要素を追加した後、元の配列の参照を新しい配列setArray(newElements)に指します.

  • このような利点は、現在のコンテナには要素が追加されていないため、CopyOnWriteコンテナを同時に読むことができ、読み取りと書き込みがそれぞれ異なるコンテナで完了するという考え方です.
    また,他のデータ構造:HashMap,HashSetのように,その下層は安全ではなく,引き起こす異常はArrayListと同様に,いずれも
    java.util.ConcurrentModificationExecptionには、ConcurrentHashMap、CopyOnWriteArraySetという解決策があります.
    CopyOnWriteArraySetお笑いソース
    public class CopyOnWriteArraySet extends AbstractSet
            implements java.io.Serializable {
        private static final long serialVersionUID = 5457747651344034263L;
    
        private final CopyOnWriteArrayList al;
    
        /**
         * Creates an empty set.
         */
        public CopyOnWriteArraySet() {
            al = new CopyOnWriteArrayList();
        }
    }

    その下位にCopyOnWriteArrayListがあるのか・・・スープと薬を交換しないで・・・
    ========================================================================
    追加知識:HashSetの最下層はHashMapであり、証拠は以下のソースコードである.
    public class HashSet
        extends AbstractSet
        implements Set, Cloneable, java.io.Serializable
    {
        static final long serialVersionUID = -5024744406713321676L;
    
        private transient HashMap map;
    
        // Dummy value to associate with an Object in the backing Map
        private static final Object PRESENT = new Object();
    
        /**
         * Constructs a new, empty set; the backing HashMap instance has
         * default initial capacity (16) and load factor (0.75).
         */
        public HashSet() {
            map = new HashMap<>();
        }
    }

    それでは問題が来ました!!!


    HashMap添加元素はmapである.put(key,value);HashSetの追加要素はsetである.add(Element);この二つはまったく違うのに,どうしてつながっているのか.
    あまり話さないで、やはりソースを見ます.
    public class HashSet extends AbstractSet implements Set, Cloneable, java.io.Serializable
    {
        static final long serialVersionUID = -5024744406713321676L;
    
        private transient HashMap map;
    
        // Dummy value to associate with an Object in the backing Map
        private static final Object PRESENT = new Object();
    
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
    }

    何だ!?HashSetのaddメソッドにはHashMapのputメソッドがあり、要素eがkeyの位置に追加され、valueの位置は定数タイプのObjectで、
    名前はPRESENT--プレゼント・・・