「原創」Java並發編程系列28|Copy-On-Write容器

2020-12-14 酷扯兒

本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫

正文

前面兩篇講了並發編程中線程安全HashMap:

ConcurrentHashMap

,那麼作為同樣使用頻率很高的List和Set,J.U.C當然也提供了相應的線程安全集合,就是

Copy-On-Write

容器

CopyOnWriteArrayList

CopyOnWriteArraySet

COW設計思想源碼分析應用場景1. COW思想

這裡的COW當然不是奶牛,而是

Copy-On-Write

的簡稱,即寫時複製,是一種用於程序設計中的優化策略。

1.1 COW原理

COW的基本思路:

當讀取共享數據時,直接讀取,不需要有其他操作(比如阻塞等待、複製等)。當寫共享數據時,將舊數據複製出來一份作為新數據,只修改新數據,修改完新數據之後將新數據的引用賦值給原來數據的引用。在整個寫數據的過程中,所有讀取共享數據的操作都是讀的舊數據。COW容器只有寫操作與寫操作之間是互斥的,讀讀和讀寫都不互斥。

1.2 COW優缺點分析

優點:

效率高。因為COW保證讀和寫操作的不是同一份數據,共享數據在讀和寫時都不需要阻塞其他來讀取數據的線程,所以COW有很高的效率。保證數據一致性。因為COW保證讀和寫操作的不是同一份數據,讀數據的操作不會讀到寫了一半的數據,所以能夠保證數據的最終一致性。缺點:

數據實時性差。COW在寫數據完成之前一直讀取舊數據,而寫數據又包括複製和修改的操作,花費時間較長,導致數據實時性較差。其實COW的設計思想就是通過犧牲數據的實時性來保證數據一致性的。內存佔用大。COW中有一步複製操作,將舊數據複製出來一份作為新數據,假如舊數據本身比較大,那麼新數據也要佔用同樣大的內存空間。類似空間換時間的思想,這裡用空間換數據一致性,當然也換取了讀取數據的時間。1.3 COW應用

COW的設計思想的一些應用:內存管理(如Linux的fork()函數),數據存儲(如redis),文件管理系統(如Linux的文件管理系統),軟體開發(如Java的Copy-On-Write容器)。

2. 源碼分析

理解了

Copy-On-Write

思想,

CopyOnWriteArrayList

CopyOnWriteArraySet

的源碼就很容易了。本文以

CopyOnWriteArrayList

源碼為例來分析

Copy-On-Write

容器。

2.1 類結構

CopyOnWriteArrayList

只有兩個屬性,數組array用於存儲數據,重入鎖lock用於寫操作的同步。

public class CopyOnWriteArrayList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable {final transient ReentrantLock lock = new ReentrantLock(); private transient volatile Object[] array;}

2.2 get()

get()方法獲取數據,真的不用注釋和講解,最簡單的代碼。唯一需要注意的一點就是get()方法是沒有加鎖的,不需要同步,讀數據線程一定不會阻塞。

public E get(int index) {return get(getArray(), index);}final Object[] getArray() { return array;}private E get(Object[] a, int index) { return (E) a[index];}

2.3 add()

代碼很簡單,基本過程就是按照COW思想的操作步驟:

lock鎖同步舊數組複製出一個新數組新數組添加元素新數組引用賦給array

public boolean add(E e) {final ReentrantLock lock = this.lock; lock.lock();// 1. lock鎖同步 try { // 2. 舊數組複製出一個新數組 Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); // 3. 新數組添加元素 newElements[len] = e; // 4. 新數組引用賦給array setArray(newElements); return true; } finally { lock.unlock();// 解鎖 }}

3. 應用場景

Copy-On-Write並發容器用於讀多寫少的並發場,如商品的訪問和更新,排行榜,白名單/黑名單等。

舉例:一個充值排行榜的功能,排行榜會有很多人查看訪問,但是只有充值之後才會修改排行榜上的數據,或者充值之後也不更新,只有每天晚上9點更新排行榜,標準的讀多寫少。

public class CopyOnWriteArrayListTest {public static CopyOnWriteArrayList<Integer> rankIds = new CopyOnWriteArrayList<Integer>(); public static void addRankIds(int id) { /* * 獲取id在rankIds中的排序,代碼省略 * 假設id應該在排行榜中的第一個 */ rankIds.add(0, id); }}

4. 總結

Copy-On-Write並發容器處理並發問題的原理:

當讀取共享數據時,直接讀取,不需要有其他操作(比如阻塞等待、複製等)。當寫共享數據時,將舊數據複製出來一份作為新數據,只修改新數據,修改完新數據之後將新數據的引用賦值給原來數據的引用。在整個寫數據的過程中,所有讀取共享數據的操作都是讀的舊數據。源碼的並不只在於學習編程方法,更重要的是理解源碼的設計思想,能夠在開發和設計中運用。

相關焦點

  • 原創】Java並發編程系列01|開篇獲獎感言
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫為什麼要學並發編程我曾聽一個從事15年開發工作的技術人員說過,他剛工作時的並發編程第一原則就是不要寫並發程序。
  • 「原創」Java並發編程系列18|讀寫鎖(下)
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫本文為何適原創並發編程系列第 18 篇,文末有本系列文章匯總。上篇為【原創】Java並發編程系列17 | 讀寫鎖八講(上),沒看過的可以先看看。本文是下篇,從「源碼分析寫鎖的獲取與釋放」開始。7.
  • 「原創」Java並發編程系列09|基礎乾貨
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫本文為何適原創並發編程系列第9篇。現在,我們進入正題:介紹並發編程的基礎性概念。
  • 「原創」Java並發編程系列02|並發編程三大核心問題
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫寫在前面編寫並發程序是比較困難的,因為並發程序極易出現Bug,這些Bug有都是比較詭異的,很多都是沒辦法追蹤,而且難以復現。
  • 「原創」Java並發編程系列06|你不知道的final
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫final在Java中是一個保留的關鍵字,可以修飾變量、方法和類。那麼fianl在並發編程中有什麼作用呢?本文就在對final常見應用總結基礎上,講解final並發編程中的應用。
  • 「原創」Java並發編程系列29|ConcurrentLinkedQueue
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫J.U.C 為常用的集合提供了並發安全的版本,前面講解了 map 的並發安全集合 ConcurrentHashMap,List 並發安全集合 CopyOnWriteArrayList,Set 並發安全集合
  • 「原創」Java並發編程系列14|AQS源碼分析
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫本文為何適原創並發編程系列第 14 篇,文末有本系列文章匯總。AbstractQueuedSynchronizer是Java並發包java.util.concurrent的核心基礎組件,是實現Lock的基礎。
  • JAVA並發編程:並發問題的根源及主要解決方法
    而在java中,不變性變量即通過final修飾的變量,如String,Long,Double等類型都是Immutability的,它們的內部實現都是基於final關鍵字的。那這又和並發編程有什麼關係呢?其實啊,並發問題很大部分原因就是因為線程切換破壞了原子性,這又導致線程隨意對變量的讀寫破壞了數據的一致性。
  • 「原創」Java並發編程系列03|重排序-可見性和有序性問題根源
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫思維導圖寫在前面並發編程的三大問題:原子性、可見性、有序性。從java原始碼到最終實際執行的指令序列,會分別經歷下面三種重排序:編譯器優化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序。指令級並行的重排序。
  • NO.001- 簡說 Java 並發編程史
    這篇文章是Java並發編程思想系列的第一篇,主要從理解Java並發編程歷史的原因和Java並發演進過程兩部分,以極簡地回溯並發編程的歷史,幫助大家從歷史這個角度去了解一門語言一個特性的演進。對歷史理解的越多,思考的越多,未來的方向就會更加堅定。我是誰?從哪來?到哪去?
  • 「原創」Java並發編程系列33|深入理解線程池(上)
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫並發編程必不可少的線程池,接下來分兩篇文章介紹線程池,本文是第一篇。介紹1.1 使用場景並發編程可以高效利用CPU資源,提升任務執行效率,但是多線程及線程間的切換也伴隨著資源的消耗。當遇到單個任務處理時間比較短,但需要處理的任務數量很大時,線程會頻繁的創建銷毀,大量的時間和資源都會浪費在線程的創建和銷毀上,效率很低。
  • Java並發編程系列20|StampedLock源碼解析
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫本文為何適原創並發編程系列第 20 篇,文末有本系列文章匯總。上一篇介紹了StampedLock存在的意義以及如何使用,按照這個系列的風格大家也應該猜到了,這一篇就是的源碼分析。
  • 「原創」Java並發編程系列36|FutureTask
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫線程池源碼中出現了很多Callable、Future、FutureTask等以前沒介紹過的接口,尤其是線程池提交任務時總是把任務封裝成FutureTask,今天就來為大家解惑:
  • Java的synchronized 能防止指令重排序嗎?
    「二胖」:好吧,你既然這麼好奇,那我就大概說下吧,你搬上小板凳仔細挺好了哦。我要開始我的表演了。下面二胖第一面開始了。「面試官」:二胖是吧,先做個自我介紹吧。「二胖」:好的,我叫二胖,我來自長沙,今年25歲,從事java開發快3年了,現在在XX公司XX事業部擔任高級「java」開發工程師,主要負責XX系統。。。。。
  • Alibaba架構師從零開始,一步一步帶你進入並發編程的世界
    如果你是一名並發編程初學者,建議按照順序閱讀本書,並按照書中的例子進行編碼和實戰。如果你有一定的並發編程經驗,可以把本書當做一個手冊, 直接看需要學習的章節。以下是各章節的基本介紹。第l章介紹Java並發編程的挑戰,向讀者說明進入並發編程的世界可能會遇到哪些問題,以及如何解決。
  • Java並發編程系列23|循環屏障CyclicBarrier
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫本篇介紹第二個並發工具類CyclicBarrier,CyclicBarrier的字面意思是可循環使用(Cyclic)的屏障(Barrier),分以下部分介紹:
  • 「原創」你所不知道的讀寫鎖
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫大家好,我是告白,算起來自己真的是好久沒有寫作了,最近承蒙師長大大指點,決定還是記錄一下自己的一些感想感悟。也決定好好打磨一下自己的寫作水平,與大家共同進步!
  • Github上星標85.7k的並發編程神筆記也太香了吧!
    並發編程並發編程可選擇的方式有多進程、多線程和多協程。對於Java來說,它既不像C++那樣,在運行中調用Linux的系統API去"fork" 出多個進程:也不像Go那樣,在語言層面原生提供多協程。在Java中, 並發就是多線程模式。對於人腦的認知來說,「代碼一行行串行」當然最容易理解。但在多線程下,多個線程的代碼交叉並行,要訪問互斥資源,要互相通信。作為開發者,需要仔細設計線程之間的互斥與同步,稍不留心,就會寫出非線程安全的代碼。正因此,多線程編程一直是一個被廣泛而深入討論的領域。
  • Java並發編程系列21|Condition-Lock的等待通知
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫我們知道 synchronized 鎖通過 Object 類的 wait()和 notify()方法實現線程間的等待通知機制,而比 synchronized 更靈活 Lock 鎖同樣也有實現等待通知機制的方式
  • 「原創」JVM系列03|Java棧—方法是如何調用的?
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫本文是何適 JVM 修仙系列第 3 篇,文末有本系列文章匯總。 e.printStackTrace(); } }}設置參數-Xss128K 執行以上代碼,結果如下:調用深度count=1088java.lang.StackOverflowError