1 简述

CopyOnWriteArrayList 是从 JDK5 开始引进的并发集合之一,另一个是 CopyOnWriteArraySet,JDK 并没有提供 Map 的实现,我们之后将实现它。

2 COW

Copy-On-Write简称COW,是一种用于程序设计中的优化策略。思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。这种策略使得我们可以并发的读,而不用获取全局锁(但是写则需要获取锁)。体现的是读写分离的思想。但是因为每次添加都会复制原有的数组内容,因此存在内存开销问题。相对的,其他 Concurrent* 类的读写都会获取锁。

2.1 add 源码分析

 1     // 使用 volatile 类型,只通过   getArray/setArray 访问
 2     private transient volatile Object[] array;
 3 
 4    @Override
 5     public boolean add(E e) {
 6         // 获取锁
 7         final ReentrantLock lock = this.lock;
 8         lock.lock();
 9         try {
10 
11             Object[] elements = getArray();
12             int len = elements.length;
13             // 复制原有的数组
14             Object[] newElements = Arrays.copyOf(elements, len + 1);
15             newElements[len] = e;
16             // 将原有数组指向新数组
17             setArray(newElements);
18             return true;
19         } finally {
20             lock.unlock();
21         }
22     }

2.2 get 源码分析

1     public E get(int index) {
2         // 获取现数组,不需要加锁,所以可能会获取到旧的数据(当存在另一个线程对 List 进行写操作是)
3         return get(getArray(), index);
4     }

根据 2.1 ,这个方法获取到的数组可能会是的。

3 实现 CopyOnWriteMap 

遵循上边的设计,我们可以围绕 COW 设计出 Map

  1 package cn.pancc.purejdk.concurrent.fenxi;
  2 
  3 import java.util.Collection;
  4 import java.util.HashMap;
  5 import java.util.Map;
  6 import java.util.Set;
  7 import java.util.concurrent.locks.ReentrantLock;
  8 
  9 /**
 10  * @author pancc
 11  * @version 1.0
 12  */
 13 public class CopyOnWriteMap<K, V> implements Map<K, V>, Cloneable, java.io.Serializable {
 14     private static final long serialVersionUID = -6907385757412808108L;
 15     private volatile Map<K, V> map = new HashMap<>();
 16 
 17     public Map<K, V> getMap() {
 18         return map;
 19     }
 20 
 21     public void setMap(Map<K, V> map) {
 22         this.map = map;
 23     }
 24 
 25     private final ReentrantLock lock = new ReentrantLock();
 26 
 27     @Override
 28     public int size() {
 29         return getMap().size();
 30     }
 31 
 32     @Override
 33     public boolean isEmpty() {
 34         return getMap().isEmpty();
 35     }
 36 
 37     @Override
 38     public boolean containsKey(Object key) {
 39         return getMap().containsKey(key);
 40     }
 41 
 42     @Override
 43     public boolean containsValue(Object value) {
 44         return getMap().containsValue(value);
 45     }
 46 
 47     @Override
 48     public V get(Object key) {
 49         return getMap().get(key);
 50     }
 51 
 52     @Override
 53     public V put(K key, V value) {
 54         lock.lock();
 55         try {
 56             Map<K, V> newMap = new HashMap<>(map);
 57             V v = newMap.put(key, value);
 58             this.map = newMap;
 59             return v;
 60         } finally {
 61             lock.unlock();
 62         }
 63     }
 64 
 65     @Override
 66     public V remove(Object key) {
 67         lock.lock();
 68         try {
 69             Map<K, V> newMap = new HashMap<>(map);
 70             V v = newMap.remove(key);
 71             this.map = newMap;
 72             return v;
 73         } finally {
 74             lock.unlock();
 75         }
 76     }
 77 
 78     @Override
 79     public void putAll(Map<? extends K, ? extends V> m) {
 80         lock.lock();
 81         try {
 82             Map<K, V> newMap = new HashMap<>(map);
 83             newMap.putAll(m);
 84             this.map = newMap;
 85         } finally {
 86             lock.unlock();
 87         }
 88     }
 89 
 90     @Override
 91     public void clear() {
 92         lock.lock();
 93         try {
 94             this.map = new HashMap<>();
 95         } finally {
 96             lock.unlock();
 97         }
 98     }
 99 
100     @Override
101     public Set<K> keySet() {
102         return getMap().keySet();
103     }
104 
105     @Override
106     public Collection<V> values() {
107         return getMap().values();
108     }
109 
110     @Override
111     public Set<Entry<K, V>> entrySet() {
112         return getMap().entrySet();
113     }
114 }

4 COW 的缺点

  • 内存占用问题:每次写操作都会执行复制操作
  • 数据一致性问题:写操作并不会及时影响到读操作获取的数据

5 应用场景

适用于读多写少的场景,如黑名单机制,单位信息缓存

posted on 2020-03-13 15:08  四维胖次  阅读(165)  评论(0编辑  收藏  举报