「java.util.concurrent并发包5」之 CopyOnWrite

一 概述

Copy-On-Write即写时复制的容器,是一种用于程序设计中的优化策略。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读而不需要加锁。所以CopyOnWrite容器也是一种读写分离的思想,读和写使用不同的容器。

从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet

 

 

二 实现原理

核心就是读时候不加锁,写时候进行同步,copy容器 -> 引用变更,copyOnWriteArrayList用到底层native的System.arraycopy方法。

下面仿照源码写的MyCopyOnWriteArrayList(看起来👇的setArray是为了gc)

1 public class MyCopyOnWriteArrayList<E> {
  2 
  3     private transient final ReentrantLock reentrantLock = new ReentrantLock();
  4 
  5     private volatile transient Object[] array;
  6 
  7     public Object[] getArray() {
  8         return array;
  9     }
 10 
 11     public void setArray(Object[] array) {
 12         this.array = array;
 13     }
 14 
 15     public MyCopyOnWriteArrayList() {
 16         setArray(new Object[0]);
 17     }
 18 
 19     public MyCopyOnWriteArrayList(Collection<? extends E> collection) {
 20         Object[] elements = collection.toArray();
 21         if (elements.getClass() != Object[].class) {
 22             elements = Arrays.copyOf(elements, elements.length, Object[].class);
 23         }
 24         setArray(elements);
 25     }
 26 
 27     public MyCopyOnWriteArrayList(E[] toCopyIn) {
 28         setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
 29     }
 30 
 31     public int size() {
 32         return getArray().length;
 33     }
 34 
 35     public boolean isEmpty() {
 36         return size() == 0;
 37     }
 38 
 39     /**
 40      * 返回Object类型数组
 41      */
 42     public Object[] toArray() {
 43         Object[] elements = getArray();
 44         return Arrays.copyOf(elements, elements.length);
 45     }
 46 
 47     /**
 48      * 返回传入的T类型数组
 49      */
 50     @SuppressWarnings("unchecked")
 51     public <T> T[] toArray(T a[]) {
 52         Object[] elements = getArray();
 53         int len = size();
 54         if (a.length < len) {
 55             return (T[]) Arrays.copyOf(elements, len, a.getClass());
 56         } else {
 57             System.arraycopy(elements, 0, a, 0, len);
 58             return a;
 59         }
 60     }
 61 
 62     @SuppressWarnings("unchecked")
 63     private E get(Object[] a, int index) {
 64         return (E) a[index];
 65     }
 66 
 67     public E get(int index) {
 68         return get(getArray(), index);
 69     }
 70 
 71     public E set(int index, E element) {
 72         final ReentrantLock reentrantLock = this.reentrantLock;
 73         reentrantLock.lock();
 74         try {
 75             Object[] elements = getArray();
 76             E oldValue = get(elements, index);
 77 
 78             if (oldValue != element) {
 79                 int len = elements.length;
 80                 Object[] newElements = Arrays.copyOf(elements, len);
 81                 newElements[index] = element;
 82                 setArray(newElements);
 83             } else {
 84                 // 意义何在,没太看懂
 85                 setArray(elements);
 86             }
 87             return oldValue;
 88         } finally {
 89             reentrantLock.unlock();
 90         }
 91     }
 92 
 93     public boolean add(E e) {
 94         final ReentrantLock reentrantLock = this.reentrantLock;
 95         reentrantLock.lock();
 96         try {
 97             Object[] elements = getArray();
 98             int len = size();
 99             Object[] newElements = Arrays.copyOf(elements, len + 1);
100             newElements[len] = e;
101             setArray(newElements);
102             return true;
103         } finally {
104             reentrantLock.unlock();
105         }
106     }
107 
108     public E remove(int index) {
109         final ReentrantLock reentrantLock = this.reentrantLock;
110         reentrantLock.lock();
111         try {
112             Object[] elements = getArray();
113             int len = elements.length;
114             E oldValue = get(elements, index);
115             int numMoved = len - index - 1;
116             if (numMoved == 0)
117                 setArray(Arrays.copyOf(elements, len - 1));
118             else {
119                 Object[] newElements = new Object[len - 1];
120                 System.arraycopy(elements, 0, newElements, 0, index);
121                 System.arraycopy(elements, index + 1, newElements, index,
122                         numMoved);
123                 setArray(newElements);
124             }
125             return oldValue;
126         } finally {
127             reentrantLock.unlock();
128         }
129     }
130 }

 

那么再自己实现一个MyCopyOnWriteHashMap

1 public class MyCopyOnWriteHashMap<K, V> {
 2 
 3     private transient final ReentrantLock reentrantLock = new ReentrantLock();
 4 
 5     private volatile transient HashMap<K, V> hashMap;
 6 
 7     public MyCopyOnWriteHashMap(int expectedSize) {
 8         hashMap = Maps.newHashMapWithExpectedSize(expectedSize);
 9     }
10 
11     public V put(K k, V v) {
12         reentrantLock.lock();
13         try {
14             HashMap<K, V> curHashMap = Maps.newHashMap(hashMap);
15             V newV = curHashMap.put(k, v);
16             hashMap = curHashMap;
17             return newV;
18         } finally {
19             reentrantLock.unlock();
20         }
21     }
22 
23     public void putAll(Map<? extends K, ? extends V> map) {
24         reentrantLock.lock();
25         try {
26             HashMap<K, V> curHashMap = Maps.newHashMap(hashMap);
27             curHashMap.putAll(map);
28             hashMap = curHashMap;
29         } finally {
30             reentrantLock.unlock();
31         }
32     }
33 
34     public V get(K k) {
35         return hashMap.get(k);
36     }
38 }

 

 

三 应用场景和问题

CopyOnWrite并发容器用于读多写少的并发场景。比如黑白名单

注意一下每次copy的开销,可预见的size可以提前设定; 另外容器可以批量添加等,避免多次的容器复制扩容

由于CopyOnWrite的写时复制机制,写操作过程中,内存里会同时驻扎两个对象的内存,更新大对象时可能造成频繁的GC,会拖垮应用响应时间

CopyOnWrite容器只保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的数据立即被读取响应,请不要用这个容器。

posted @ 2017-11-10 14:56  balfish  阅读(398)  评论(0编辑  收藏  举报