JUC併發程式設計學習(五)集合類不安全

高同學,你好發表於2023-11-03

集合類不安全

List不安全

單執行緒情況下集合類和很多其他的類都是安全的,因為同一時間只有一個執行緒在對他們進行修改,但是如果是多執行緒情況下,那麼集合類就不一定是安全的,可能會出現一條執行緒正在修改的同時另一條執行緒啟動來對這個集合進行修改,這種情況下就會導致發生併發修改異常(在jdk11的環境下多次測試該程式碼發現並無問題,但是學習教程中有該異常。原因:執行緒數量不夠)

package org.example.unsafe;

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

public class Test1 {
    public static void main(String[] args) {
        ArrayList<String> sts = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                sts.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(sts);
            },String.valueOf(i)).start();
        }


    }
}

重現該異常,透過for迴圈開更多執行緒

package org.example.unsafe;

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

public class Test1 {
    public static void main(String[] args) {
        MidiFireList midiFireList = new MidiFireList();


        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                midiFireList.midi();
            }, "A").start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                midiFireList.midi();
            }, "B").start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                midiFireList.midi();
            }, "C").start();
        }


    }

}

class MidiFireList {
    ArrayList<String> sts = new ArrayList<>();

    public void midi() {
        sts.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
        System.out.println(sts);
    }
}

成功重現異常

解決List的併發修改異常

1、透過使用List的子類Vector來操作,Vector預設時執行緒安全的,所以不會出現以上情況,Vector時jdk1.0時期就出現的,它的add方法使用了synchronized關鍵字來保證執行緒安全。

class MidiFireList {
    //使用了執行緒安全的Vector集合類
    List<String> sts = new Vector<>();

    public void midi() {
        sts.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
        System.out.println(sts);
    }
}

2、透過所有集合的父類Collections類的執行緒安全的方法建立一個ArraryList。

class MidiFireList {
    List<String> sts = Collections.synchronizedList(new ArrayList<String>());

    public void midi() {
        sts.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
        System.out.println(sts);
    }
}

3、透過JUC包下的CopyOnWriteArrayList類來建立一個ArrayList,他內部的方法透過同步程式碼塊和lock鎖實現了執行緒安全的各種操作,缺點時少量執行緒操作時成本太高(CopyOnWrite寫入時複製,COW思想,是計算機程式設計領域中的一種最佳化策略),在寫入時複製一份,避免覆蓋導致資料問題,讀寫分離思想

CopyOnWriteArrayList和Vector的線上程安全方面的區別,為什麼要用CopyOnWriteArrayList

CopyOnWriteArrayList對比Vector,我們可以透過原始碼來看

CopyOnWriteArrayList:

Vector:

jdk1.8時的CopyOnWriteArrayList:

其實在jdk11之後的區別只在於同步程式碼塊和同步方法的區別,可參考同步程式碼塊和同步方法有什麼區別 • Worktile社群瞄一眼CopyOnWriteArrayList(jdk11) - 傅曉芸 - 部落格園 (cnblogs.com)這兩篇文章。

但是在jdk1.8時,CopyOnWriteArrayList的方法時單純的透過Lock鎖來實現同步的,沒有使用synchronized關鍵字,因為會影響效能。

Set不安全

Set的不安全問題與List一樣,解決方案如下

1、透過Collections的同步方法來建立一個執行緒安全的Set

class MidiFireList {
    Set<String> set = Collections.synchronizedSet(new HashSet<>());

    public void midi() {
        set.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
        System.out.println(set);
    }
}

2、透過CopyOnWriteArraySet類來建立執行緒安全的Set

class MidiFireList {
    Set<String> set = new CopyOnWriteArraySet<>();

    public void midi() {
        set.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
        System.out.println(set);
    }
}

HashSet的底層就是HashMap,他就不是一個新的東西

HashSet的add方法就時HashMap的put方法封裝了一下

map的key是無法重複的,所以HashSet是無序的

Map不安全

Map解決方案

package org.example.unsafe;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class MapTest {
    public static void main(String[] args) {
//        HashMap是這樣用的嗎?不是工作中不用HashMap
//        預設等價於什麼? new HashMap<>(16,0.75);
        Map<String, String> map = new ConcurrentHashMap<>();
//        載入因子、初始化容量

        for (int i = 0; i < 50; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }

    }
}

注意Map的併發類為ConcurrentHashMap

相關文章