一 CopyOnWrite容器概述
Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
二 CopyOnWriteArrayList的实现原理及引申
核心就是读时候不加锁,写时候进行同步,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 }
MyCopyOnWriteArrayList
那么再自己实现一个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 }
37
38 }
MyCopyOnWriteHashMap
三 CopyOnWrite的应用场景和问题
CopyOnWrite并发容器用于读多写少的并发场景。比如黑白名单,etc.
注意一下每次copy的开销,可预见的size可以提前设定; 另外容器可以批量添加等,避免多次的容器复制扩容
由于CopyOnWrite的写时复制机制,写操作过程中,内存里会同时驻扎两个对象的内存,更新大对象时可能造成频繁的GC,会拖垮应用响应时间
CopyOnWrite容器只保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的数据立即被读取响应,请不要用这个容器。