本文轉載自【微信公眾號: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並發容器處理並發問題的原理:
當讀取共享數據時,直接讀取,不需要有其他操作(比如阻塞等待、複製等)。當寫共享數據時,將舊數據複製出來一份作為新數據,只修改新數據,修改完新數據之後將新數據的引用賦值給原來數據的引用。在整個寫數據的過程中,所有讀取共享數據的操作都是讀的舊數據。源碼的並不只在於學習編程方法,更重要的是理解源碼的設計思想,能夠在開發和設計中運用。