1. 程式人生 > 其它 >JUC(4):集合類不安全

JUC(4):集合類不安全

技術標籤:JUC併發程式設計java多執行緒

List不安全

在之前我們寫list相關的程式碼時,大部分都是用的單執行緒,所以不存線上程安全問題!

public class ListTest {
    public static void main(String[] args) {
    //併發下 ArrayList 不安全
    List<String> list = new ArrayList<>();
    for (int i =1 ;i <=100 ;i++){
        new Thread(()->{
            list.
add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); },String.valueOf(i)).start(); } } }

執行之後會報錯:java.util.ConcurrentModificationException 併發修改異常。

解決的方法:

1.用Vector代替ArrayList;不建議使用!檢視底層可以發現,ArrayList的add方法沒有被Synchronized修飾,而Vector的是被Synchronized修飾的!

2.使用Collections工具類,將上面的程式碼改為:

    List<String> list = Collections.synchronizedList(new ArrayList<>());

上述兩種方法是常人最容易想到的!
3.使用CopyOnWriteArrayList:其中CopyOnWrite 寫入時複製,又叫COW 是計算機程式設計領域的一種優化策略!(推薦使用)

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

問題:為什麼使用CopyOnWriteArrayList而不使用Vector?

Vector和CopyOnWriteArrayList都是執行緒安全的List,底層都是陣列實現的,Vector的每個方法都進行了加鎖,而CopyOnWriteArrayList的讀操作是不加鎖的,因此CopyOnWriteArrayList的讀效能遠高於Vector,Vector每次擴容的大小都是原來陣列大小的2倍,而CopyOnWriteArrayList不需要擴容,通過COW思想就能使陣列容量滿足要求。兩個集合都實現了RandomAccess介面,支援隨機讀取,因此更加推薦使用for迴圈進行遍歷。在開發中,讀操作會遠遠多於其他操作,因此使用CopyOnWriteArrayList集合效率更高。

什麼是寫入時複製(COW)的思想?

寫入時複製(CopyOnWrite,簡稱COW)思想是計算機程式設計領域中的一種通用優化策略。其核心思想是,如果有多個呼叫者(Callers)同時訪問相同的資源(如記憶體或者是磁碟上的資料儲存),他們會共同獲取相同的指標指向相同的資源,直到某個呼叫者修改資源內容時,系統才會真正複製一份專用副本(private copy)給該呼叫者,而其他呼叫者所見到的最初的資源仍然保持不變。這過程對其他的呼叫者都是透明的(transparently)。此做法主要的優點是如果呼叫者沒有修改資源,就不會有副本(private copy)被建立,因此多個呼叫者只是讀取操作時可以共享同一份資源。

通俗易懂的講,寫入時複製技術就是不同程序在訪問同一資源的時候,只有更新操作,才會去複製一份新的資料並更新替換,否則都是訪問同一個資源。

JDK 的 CopyOnWriteArrayList/CopyOnWriteArraySet 容器正是採用了 COW 思想,它是如何工作的呢?簡單來說,就是平時查詢的時候,都不需要加鎖,隨便訪問,只有在更新的時候,才會從原來的資料複製一個副本出來,然後修改這個副本,最後把原資料替換成當前的副本。修改操作的同時,讀操作不會被阻塞,而是繼續讀取舊的資料。這點要跟讀寫鎖區分一下。

Set不安全

同樣的,使用Set多執行緒也是不安全的。


public class SetTest {
    public static void main(String[] args) {
       Set<Object> set = new HashSet<>();
        for (int i =1 ;i <=100 ;i++){
        new Thread(()->{
            set.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(set);
        },String.valueOf(i)).start();
    }
    }
}

也會發生java.util.ConcurrentModificationException 併發修改異常。

解決方法:

1.使用Collections工具類,將上面的程式碼改為:

   Set<String>  set = Collections.synchronizedList(new HashSet<>());

2.使用CopyOnWriteArraySet:

Set<Object> set = new CopyOnWriteArraySet<>();

問題:HashSet的底層是什麼?
在這裡插入圖片描述
其中Set的add方法底層:
在這裡插入圖片描述
在這裡插入圖片描述

HsahSet底層使用的是HashMap;可以看到add方法的本質就還是map,其中的e就是map中的key,PRESENT就是一個不變的值。

Map 不安全

回顧HashMap

public class MapTest {
    public static void main(String[] args) {
       Map<String, String> map = new HashMap<>();
       for (int i = 0; i <=30 ;i++){
           new Thread(()->{
               map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
               System.out.println(map);
           },String.valueOf(i)).start();
       }
    }
}

也會發生java.util.ConcurrentModificationException 併發修改異常。

解決方法

1.使用ConcurrentHashMap

Map<String, String> map = new ConcurrentHashMap<>();

注意的是:ConcurrentHashMap不能接受null的key和null的value,會丟擲空指標異常的!HashMap不能保證元素的順序,HashMap能夠將鍵設為null,也可以將值設為null。