高并發(fā)場景線程安全的List

為什么Vector和Collections.SynchronizedList的get方法要加鎖呢?

1. 線程不安全的ArrayList

為什么說ArrayList是線程不安全的:

  1. add()操作拋出數(shù)組越界異常;
  2. add()操作會(huì)丟失元素;
  3. set()操作去修改元素,get()操作去獲取元素時(shí),可以讀到新值也可能讀到舊值,無法保證一致性。

源碼分析:

//存放list集合元素的數(shù)組,默認(rèn)容量10
transient Object[] elementData; 
//list大小
private int size;

add()的源碼:

public boolean add(E e) {
    //確定添加元素之后,集合的大小是否足夠,若不夠則會(huì)進(jìn)行擴(kuò)容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //插入元素
    elementData[size++] = e;
    return true;
}

場景1:多個(gè)線程都沒進(jìn)行擴(kuò)容,但是執(zhí)行了elementData[size++] = e;時(shí),便會(huì)出現(xiàn)“數(shù)組越界異?!?;
場景2:因?yàn)閟ize++本身就是非原子性的,多個(gè)線程之間訪問沖突,這時(shí)候兩個(gè)線程可能對同一個(gè)位置賦值,就會(huì)出現(xiàn)“size小于期望值的結(jié)果”;

2. Vector和Collections.SynchronizedList的get方法要加鎖呢?

get()操作時(shí)集合中的元素不能并發(fā)的被修改,否則就易出現(xiàn)數(shù)據(jù)問題。

  1. Vector和Collections.SynchronizedList的get方法加了synchronized后可以保證順序性與實(shí)時(shí)一致性,當(dāng)一個(gè)線程在讀取數(shù)據(jù)時(shí),一定可以看到其他線程解鎖前寫入的全部數(shù)據(jù)。
  2. 并且Vector和Collections.SynchronizedList的數(shù)組并沒有用volatile修飾,如果不加鎖,也無法保證可見性。

3. 線程安全的3種List集合

//方法上使用sync關(guān)鍵字(讀寫均加鎖)
Vector vector = new Vector();
//寫操作每一次均copy一個(gè)數(shù)組,讀操作不加鎖(寫加鎖性能低,讀不加鎖性能極高)
CopyOnWriteArrayList<Integer> r2 = new CopyOnWriteArrayList<>();
//使用sync代碼塊裝飾傳入List的讀寫操作(讀寫均加鎖)
List<String> r3 = Collections.synchronizedList(new ArrayList<>());
  • Vector/Collections.synchronizedList:讀寫均加鎖,來實(shí)現(xiàn)線程安全;
  • CopyOnWriteArrayList基于寫時(shí)復(fù)制技術(shù)實(shí)現(xiàn)的,讀操作無鎖(讀取快照),寫操作有鎖,體現(xiàn)了讀寫分離的思想,但是無法提供實(shí)時(shí)一致性。

4. 并發(fā)安全的案例

下面給出一個(gè)案例,即容易出現(xiàn)并發(fā)問題的場景:

public class TestList {

    private static final ExecutorService VIEW_EXECUTOR = new ThreadPoolExecutor(2,4,1000,
            TimeUnit.SECONDS,new ArrayBlockingQueue<>(2));

    /**
     * 目前比較常用的構(gòu)建線程安全的List有三種方法:
     * <p>
     * 使用Vector容器
     * 使用Collections的靜態(tài)方法synchronizedList(List< T> list)
     * 采用CopyOnWriteArrayList容器
     */
    public static void main(String[] args) {
        //常用方式:使用線程池并發(fā)處理,填充結(jié)果
        ArrayList<Object> res = new ArrayList<>();
        CompletableFuture.runAsync(() -> {
            //todo 邏輯處理
            //線程不安全,需要使用一個(gè)線程安全的List,這里推薦Collections.synchronizedList
            res.add("success");
        }, VIEW_EXECUTOR);
    }
}

推薦閱讀

重學(xué)Java并發(fā)編程(寫時(shí)復(fù)制技術(shù)在CopyOnWriteArrayList中的應(yīng)用)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容