1. 程式人生 > 其它 >Java面試題集錦(4):執行緒不安全之ArrayList、Set、Map

Java面試題集錦(4):執行緒不安全之ArrayList、Set、Map

技術標籤:java

我們知道ArrayList是執行緒不安全的,請編碼寫一個不安全的案例並給出解決方案?

一、ArrayList案例

1. 故障現象

class Scratch {
    public static void main(String[] args) {
        //List<String> list = Arrays.asList("a","b","c");
        //list.forEach(System.out::println);

        List<String>
list = new ArrayList<>(); for(int i=1;i<=3;i++){ new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); }, String.valueOf(i)).start(); } //java.util.ConcurrentModificationException
//ArrayList 併發常見的異常 } }

在這裡插入圖片描述
ArrayLIst的add方法沒有加鎖,所以有可能報併發異常java.util.ConcurrentModificationException ,這是ArrayList 併發常見的異常。
2. 導致原因
併發爭搶修改導致,一個執行緒正在寫,另一個執行緒來搶奪,導致資料不一致異常,併發修改異常。
3. 解決方案
① 改為new Vector<>();

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

vector是JDK1.0版本的List實現,add方法加synchronized,犧牲了併發性。

②使用synchronizedList

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

注:Java中Collection和Collections的區別
Collection 是一個集合介面(集合類的一個頂級介面)它提供了對集合物件進行基本操作的通用介面方法。Collection介面在Java 類庫中有很多具體的實現,其直接繼承介面有List與Set。
Collections 則是集合類的一個工具類/幫助類,其中提供了一系列靜態方法,用於對集合中元素進行排序、搜尋以及執行緒安全等各種操作。
(具體見https://www.cnblogs.com/cathyqq/p/5279859.html)
③使用CopyOnWriteArrayList

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

寫時複製:併發時,當執行緒1使用list時,先拷貝一份list,擴容1,寫完之後將原來的陣列引用修改為拷貝的陣列,並返回true。

Set案例

Set也是執行緒不安全的
1. 故障現象

class Scratch {
    public static void main(String[] args) {
        //List<String> list = Arrays.asList("a","b","c");
        //list.forEach(System.out::println);

        Set<String> set = new HashSet<>();
        for(int i=1;i<=30;i++){
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
        //java.util.ConcurrentModificationException
        //ArrayList 併發常見的異常
    }
}

在這裡插入圖片描述
Set也會報錯:ConcurrentModificationException。
2. 解決方法
①使用synchronizedSet

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

②使用CopyOnWriteArrayList

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

另注: HashSet的底層是HashMap。HashSet的key是HashMap的key,HashSet的value都等於PRESENT(Object的常量)。

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

而PRESENT是一個Object的常量。

private static final Object PRESENT = new Object();

Map案例

Map也是執行緒不安全的
1. 故障現象

class Scratch {
    public static void main(String[] args) {
        //List<String> list = Arrays.asList("a","b","c");
        //list.forEach(System.out::println);

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

        for(int i=1;i<=10;i++){
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
        //java.util.ConcurrentModificationException
        //ArrayList 併發常見的異常
    }
}

在這裡插入圖片描述
ArrayList、Set、Map丟擲的異常一樣,說明三者的底層是同一套體系。
2. 解決方法
①使用ConcurrentHashMap

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

①使用Collections.synchronizedMap

Map<String,String> map = new Collections.synchronizedMap(new HashMap<>());