ArrayList線程不安全舉例及解決

ArrayList部分源碼

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    /**
     * 列表元素集合數組
     * 如果新建ArrayList對象時沒有指定大小,那麼會將EMPTY_ELEMENTDATA賦值給elementData,
     * 並在第一次添加元素時,將列表容量設置爲DEFAULT_CAPACITY 
     */
    transient Object[] elementData; 

    /**
     * 列表大小,elementData中存儲的元素個數
     */
    private int size;
}

add方法

public boolean add(E e) {

    /**
     * 添加一個元素時,做了如下兩步操作
     * 1.判斷列表的capacity容量是否足夠,是否需要擴容
     * 2.真正將元素放在列表的元素數組裏面
     */
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

ArrayList線程不安全的表現
在多個線程進行add操作時可能會導致elementData數組越界。

public static void main(String[] args) throws InterruptedException {
      
        final List<Integer> list = new ArrayList<Integer>();

        // 線程A將0-1000添加到list
        new Thread(() -> {
            for (int i = 0; i < 1000 ; i++) {
                list.add(i);

                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        // 線程B將1000-2000添加到列表
        new Thread(() -> {
            for (int i = 1000; i < 2000 ; i++) {
                list.add(i);

                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        Thread.sleep(1000);

        // 打印所有結果
        for (int i = 0; i < list.size(); i++) {
            System.out.println("第" + (i + 1) + "個元素爲:" + list.get(i));
        }

}

在這裏插入圖片描述

多線程情況下,一個線程正在寫入,另一個線程也在寫入,導致數據不一致異常,併發生修改異常 java.util.ConcurrentModificationException

public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
        for (int i =1; i<=30 ; i++) {
            new Thread(() -> {
                list.add("a");
                list.add("b");
                list.add("c");
                list.add("d");
                System.out.println(list.toString());
            }).start();
        }
}

在這裏插入圖片描述

解決辦法


//Synchronized對代碼進行加鎖,力度大,所以代碼執行效率低下
List<String> list = Collections.synchronizedList(new ArrayList<String>());
//寫時複製通過lock機制進行枷鎖
/*CopyOnWrite容器即寫時複製的容器。往一個容器添加元索的時候,不直接往當前容器Object[]添加,
而是先將當前容器Object[]進行Copy,複製出一個新的容器object[] newElements,
然後往新的容器object[] newElements 裏添加元素,
添加完元素之後,再將原容器的引用指向新的容器setArray(newElements);。 
這樣做的好處是可以CopyOnWrite容器進行併發的讀,而不需要加鎖,因爲當前容器不會添加任何元素。
所以CopyOnWrite 容器也是一種讀寫分離的思想,讀和寫不同的容器*/
List<String> list = new CopyOnWriteArrayList();

List<String> list = new Vector<>();

CopyOnWriteArrayList add方法源碼

/**
 * 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) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

額外說下 ArrayList與LinkedList;這兩個都是接口List下的一個實現,用法都一樣,但用的場所的有點不同,ArrayList適合於進行大量的隨機訪問的情況下使用,LinkedList適合在表中進行插入、刪除時使用,二者都是非線程安全,解決方法同上(爲了避免線程安全,以上採取的方法,特別是第二種,其實是非常損耗性能的)

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