1. 程式人生 > 其它 >JUC學習筆記(四)

JUC學習筆記(四)

Vector 是向量佇列,它是 JDK1.0 版本新增的類。繼承於 AbstractList,實現了 List, RandomAccess, Cloneable 這些介面。 Vector 繼承了 AbstractList,實現了 List;所以,它是一個佇列,支援相關的新增、刪除、修改、遍歷等功能。 Vector 實現了 RandmoAccess 介面,即提供了隨機訪問功能。

JUC學習筆記(一)https://www.cnblogs.com/lm66/p/15118407.html
JUC學習筆記(二)https://www.cnblogs.com/lm66/p/15118813.html
JUC學習筆記(三)https://www.cnblogs.com/lm66/p/15118976.html

1、集合的執行緒安全

1.1、集合操作Demo

NotSafeDemo

public class NotSafeDemo {
    public static void main(String[] args) {
        List list = new ArrayList();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString());
                System.out.println(list);
            }, "執行緒" + i).start();
        }
    }
}

異常內容
java.util.ConcurrentModificationException
問題: 為什麼會出現併發修改異常?
檢視 ArrayList 的 add 方法原始碼

那麼我們如何去解決 List 型別的執行緒安全問題?

1.2、Vector

  Vector 是向量佇列,它是 JDK1.0 版本新增的類。繼承於 AbstractList,實現了 List, RandomAccess, Cloneable 這些介面。 Vector 繼承了 AbstractList,實現了 List;所以,它是一個佇列,支援相關的新增、刪除、修改、遍歷等功能。 Vector 實現了 RandmoAccess 介面,即提供了隨機訪問功能

。RandmoAccess 是 java 中用來被 List 實現,為 List 提供快速訪問功能的。在
Vector 中,我們即可以通過元素的序號快速獲取元素物件;這就是快速隨機訪問。 Vector 實現了 Cloneable 介面,即實現 clone()函式。它能被克隆。
和 ArrayList 不同,Vector 中的操作是執行緒安全的。
NotSafeDemo 程式碼修改

public class NotSafeDemo {
    public static void main(String[] args) {
        List list = new Vector();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString());
                System.out.println(list);
            }, "執行緒" + i).start();
        }
    }
}

現在沒有執行出現併發異常,為什麼?
檢視 Vector 的 add 方法

add 方法被 synchronized 同步修辭,執行緒安全!因此沒有併發異常

1.3、Collections

Collections 提供了方法 synchronizedList 保證 list 是同步執行緒安全的
NotSafeDemo 程式碼修改

public class NotSafeDemo {
    public static void main(String[] args) {
        List list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString());
                System.out.println(list);
            }, "執行緒" + i).start();
        }
    }
}

沒有併發修改異常
檢視方法原始碼

1.4、CopyOnWriteArrayList

首先我們對 CopyOnWriteArrayList 進行學習,其特點如下:
它相當於執行緒安全的 ArrayList。和 ArrayList 一樣,它是個可變陣列;但是和ArrayList 不同的時,它具有以下特性:

  • 它最適合於具有以下特徵的應用程式:List 大小通常保持很小,只讀操作遠多於可變操作,需要在遍歷期間防止執行緒間的衝突。
  • 它是執行緒安全的。
  • 因為通常需要複製整個基礎陣列,所以可變操作(add()、set() 和 remove() 等等)的開銷很大。
  • 迭代器支援 hasNext(), next()等不可變操作,但不支援可變 remove()等操作。
  • 使用迭代器進行遍歷的速度很快,並且不會與其他執行緒發生衝突。在構造迭代器時,迭代器依賴於不變的陣列快照。

獨佔鎖效率低:採用讀寫分離思想解決
寫執行緒獲取到鎖,其他寫執行緒阻塞
複製思想:

  當我們往一個容器新增元素的時候,不直接往當前容器新增,而是先將當前容器進行 Copy,複製出一個新的容器,然後新的容器裡新增元素,新增完元素之後,再將原容器的引用指向新的容器。 這時候會丟擲來一個新的問題,也就是資料不一致的問題。如果寫執行緒還沒來得及寫會記憶體,其他的執行緒就會讀到了髒資料。
這就是 CopyOnWriteArrayList 的思想和原理。就是拷貝一份。
NotSafeDemo 程式碼修改

public class NotSafeDemo {
    public static void main(String[] args) {
        List list = new CopyOnWriteArrayList();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString());
                System.out.println(list);
            }, "執行緒" + i).start();
        }
    }
}

沒有執行緒安全問題
原因分析(重點):動態陣列與執行緒安全
下面從“動態陣列”和“執行緒安全”兩個方面進一步對CopyOnWriteArrayList 的原理進行說明。

  • “動態陣列”機制
    • 它內部有個“volatile 陣列”(array)來保持資料。在“新增/修改/刪除”資料時,都會新建一個數組,並將更新後的資料拷貝到新建的陣列中,最後再將該陣列賦值給“volatile 陣列”, 這就是它叫做 CopyOnWriteArrayList 的原因
    • 由於它在“新增/修改/刪除”資料時,都會新建陣列,所以涉及到修改資料的操作,CopyOnWriteArrayList 效率很低;但是單單只是進行遍歷查詢的話,效率比較高。
  • “執行緒安全”機制
    • 通過 volatile 和互斥鎖來實現的。
    • 通過“volatile 陣列”來儲存資料的。一個執行緒讀取 volatile 陣列時,總能看到其它執行緒對該 volatile 變數最後的寫入;就這樣,通過 volatile 提供了“讀取到的資料總是最新的”這個機制的保證。
    • 通過互斥鎖來保護資料。在“新增/修改/刪除”資料時,會先“獲取互斥鎖”,再修改完畢之後,先將資料更新到“volatile 陣列”中,然後再“釋放互斥鎖”,就達到了保護資料的目的。

1.5、小結

  • 執行緒安全與執行緒不安全集合
    集合型別中存線上程安全與執行緒不安全的兩種,常見例如:
    ArrayList ----- Vector
    HashMap -----HashTable
    但是以上都是通過 synchronized 關鍵字實現,效率較低
  • Collections 構建的執行緒安全集合
  • ava.util.concurrent 併發包下
    CopyOnWriteArrayList CopyOnWriteArraySet 型別,通過動態陣列與執行緒安全個方面保證執行緒安全