JUC——安全容器類(CopyOnWriteArrayList,CopyOnWriteArraySet 和 ConcurrentHashMap)

少歌發表於2020-12-16

引入

在多執行緒下,List ,Set ,Map 都是不安全的。

先拿List舉個例子。

import java.util.*;
/**
 * @ClassName ListTest
 * @Description  
 * @Author SkySong
 * @Date 2020-10-11 22:26
 */
@SuppressWarnings("ALL")
public class ListTest {
    public static void main(String[] args) {
       
        List<String> list = new ArrayList<>();

        for (int i = 1; i <= 10; i++) {//開10條執行緒去修改list
            new Thread(()->{
                String uuid = UUID.randomUUID().toString().substring(0,5);
                list.add(uuid);
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

用多個執行緒同時去修改一個 List 列表。

併發修改異常
報了 併發修改異常,這一點是毋庸置疑的。

(java.util.ConcurrentModificationException 執行緒併發修改異常。)

解決方案

併發問題,首先想到的是 synchronize。

所以有了我們的第一個解決方案: new Vector()

import java.util.*;
/**
 * @ClassName ListTest
 * @Description  
 * @Author SkySong
 * @Date 2020-10-11 22:26
 */
@SuppressWarnings("ALL")
public class ListTest {
    public static void main(String[] args) {
       
        List<String> list = new Vector<String>();

        for (int i = 1; i <= 10; i++) {//開10條執行緒去修改list
            new Thread(()->{
                String uuid = UUID.randomUUID().toString().substring(0,5);
                list.add(uuid);
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

來看看 Vector 的 addElement() 方法:
Vector addElement()

這是最傳統的方法了。


我們還可以藉助工具類 Collections(集合老大哥)

import java.util.*;
/**
 * @ClassName ListTest
 * @Description  Collections
 * @Author SkySong
 * @Date 2020-10-11 22:26
 */
@SuppressWarnings("ALL")
public class ListTest {
    public static void main(String[] args) {
       
        List<String> list = Collections.synchronizedList(new ArrayList<>());

        for (int i = 1; i <= 10; i++) {//開10條執行緒去修改list
            new Thread(()->{
                String uuid = UUID.randomUUID().toString().substring(0,5);
                list.add(uuid);
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

我們藉助工具類 Collections 把 new 出來的 ArrayL<>() 變成一個安全的。

接下來便是我們的主角了:JUC解決方案——COW計算機優化策略



CopyOnWriteArrayList


import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @ClassName ListTest
 * @Description  java.util.ConcurrentModificationException 執行緒併發修改異常。
 * @Author SkySong
 * @Date 2020-10-11 22:26
 */
@SuppressWarnings("ALL")
public class ListTest {
    public static void main(String[] args) {
        /**
         * 解決執行緒併發問題:
         * 1.List<String> list = new Vector<String>();
         * 2.List<String> list = Collections.synchronizedList(new ArrayList<>());
         * 3.List<String> list = new CopyOnWriteArrayList<>();
         */

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

        for (int i = 1; i <= 10; i++) {//開10條執行緒去修改list
            new Thread(()->{
                String uuid = UUID.randomUUID().toString().substring(0,5);
                list.add(uuid);
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

來初步感受一下 CopyOnWriteArrayList 的設計思想:
COWListAdd

從 CopyOnWriteArrayList 的 add() 方法中可以看出,他是將 陣列資源 複製一版給呼叫者,呼叫者操作的是複製版,這樣就避免了 多個執行緒共同操作一個 “母版” 的情況。

說了這麼多,那麼JUC方案的優勢是什麼呢?

CopyOnWriteArrayList 的 add() 方法避免使用了 synchronized ,而是使用了靈活性較高的 Lock 鎖,在加上 COW 優化策略,使其效率有了很大程度的提高。

有了 List 當然 少不了 Set



CopyOnWriteArraySet


Set 和 List 都是 Collection老大哥的手下,所以很像。
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @ClassName SetTest
 * @Description java.util.ConcurrentModificationException
 * @Author SkySong
 * @Date 2020-10-12 22:57
 */
@SuppressWarnings("ALL")
public class SetTest {
    public static void main(String[] args) {
        /**
         * 解決方案:
         * 1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
         * 2.Set<String> set = new CopyOnWriteArraySet<>();
         */
        Set<String> set = new CopyOnWriteArraySet<>();

        for (int i = 1; i <= 10; i++) {//開10條執行緒去修改set
            new Thread(()->{
                String uuid = UUID.randomUUID().toString().substring(0,5);
                set.add(uuid);
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

這裡提了一下 HashSet ,簡單聊聊:
HashSet

其實 HashSet 底層就是個 HashMap。

  • HashMap 的 key 是不允許重複的,有了這一點就不難聯想了。

HashSet 的 add() 方法:
HashSet add

這裡的 value 值就是一個靜態常量,不用太在意。

List 和 Set 都說的了, 容器三劍客 怎麼能少了 Map。



ConcurrentHashMap


在多執行緒併發的情況下 Map 也是不安全的。

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

/**
 * @ClassName MapTest
 * @Description
 * @Author SkySong
 * @Date 2020-10-13 19:44
 */
public class MapTest {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName().toString(), UUID.randomUUID().toString());
                System.out.println(map);
            }).start();
        }
    }
}

同樣會報如下異常:
map併發錯誤
這裡補充一點HashMap的知識:
hashmap知識
解決思路

這裡當然可以用 Collections.synchronizedMap(new HashMap<>());

但這不是我們今天的主角。 JUC 才是我們今天的主角。

public static void main(String[] args) {
    Map<String,String> map = new ConcurrentHashMap<>();
    for (int i = 1; i <= 10; i++) {
        new Thread(()->{
            map.put(Thread.currentThread().getName().toString(), UUID.randomUUID().toString());
            System.out.println(map);
        }).start();
    }
}

關於 ConcurrentHashMap 的巧妙之處有很多。我們下次再聊!

相關文章