list線程不安全(舉例說明+三種解決)

package main.test;

import java.util.ArrayList;
import java.util.List;

/**
 * @Auther: wdq
 * @Date: 2020/4/20 08:57
 * @Description:
 */
public class ListnoSafe {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}
/*
結果爲:
a
b
c
*/

上面這個demo是我們平時用的最多的,每次運行都沒有出過錯,也太可能會出錯。

所謂的線程不安全,就是加上線程之後,list會不安全,即程序會給你搗亂。

看下面這個例子:

package main.test;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @Auther: wdq
 * @Date: 2020/4/20 08:57
 * @Description:
 */
public class ListnoSafe {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}
/*
分別執行了三次:
第一次:
[null, null, 7339634e]
[null, null, 7339634e]
[null, null, 7339634e]
第二次:
[c4b59b80, 2fd150dd, 7a38b4ae]
[c4b59b80, 2fd150dd, 7a38b4ae]
[c4b59b80, 2fd150dd, 7a38b4ae]
第三次:
[c9e6745c, cd0769c7]
[c9e6745c, cd0769c7]
[c9e6745c, cd0769c7]

按正常的思維來講,應該有三個元素,但是每次執行的結果都不一樣
原因是cpu太快了,讀寫的順序亂了。
 */

如果把循環3次改成30次,那麼有很大機率會報錯 java.util.ConcurrentModificationException;

這就是list線程不安全。

解決方法:

1、把ArrayList<>();改成Vector<>();

因爲vector是線程安全的,它的add方法的源碼:

public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}
因爲有synchronized,所以是線程安全的,能保證數據一致性但是性能卻慢了。
2、把new ArrayList<>();改成Collections.synchronizedList(new ArrayList<>());

線程安全而且性能不慢。

3、把new ArrayList();改成new CopyOnWriteArrayList<>();

JUC的方法。

    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();
        }
    }

 

CopyOnWriteArrayList容器即寫時複製。往一個容器添加元素的時候,不直接往當前容器object[]添加,而是先將當前容器object[]進行copy,複製出一個新的容器object[] newElements,然後新的容器object[] newElements裏添加元素,之後再將原容器的引用指向新的容器setArray(newElments);。這樣做的好處是可以對copyonwrite容器進行併發的讀而不需要加鎖,因爲當前容器不會添加任何元素。所以copyonwrite容器也是一種讀寫分離的思想,讀和寫不同的容器。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章