Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始你们都在共享同一个内容,当某我的想要修改这个内容的时候,才会真正把内容Copy出去造成一个新的内容而后再改,这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器很是有用,能够在很是多的并发场景中使用到。 java
public boolean add(T e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; // 复制出新数组 Object[] newElements = Arrays.copyOf(elements, len + 1); // 把新元素添加到新数组里 newElements[len] = e; // 把原数组引用指向新数组 setArray(newElements); return true; } finally { lock.unlock(); } } final void setArray(Object[] a) { array = a; }读的时候不须要加锁,若是读的时候有多个线程正在向ArrayList添加数据,读仍是会读到旧的数据,由于写的时候不会锁住旧的ArrayList。
public E get(int index) { return get(getArray(), index); }JDK中并无提供CopyOnWriteMap,咱们能够参考CopyOnWriteArrayList来实现一个,基本代码以下:
import java.util.Collection; import java.util.Map; import java.util.Set; public class CopyOnWriteMap<K, V> implements Map<K, V>, Cloneable { private volatile Map<K, V> internalMap; public CopyOnWriteMap() { internalMap = new HashMap<K, V>(); } public V put(K key, V value) { synchronized (this) { Map<K, V> newMap = new HashMap<K, V>(internalMap); V val = newMap.put(key, value); internalMap = newMap; return val; } } public V get(Object key) { return internalMap.get(key); } public void putAll(Map<? extends K, ? extends V> newData) { synchronized (this) { Map<K, V> newMap = new HashMap<K, V>(internalMap); newMap.putAll(newData); internalMap = newMap; } } }实现很简单,只要了解了CopyOnWrite机制,咱们能够实现各类CopyOnWrite容器,而且在不一样的应用场景中使用。
package com.ifeve.book; import java.util.Map; import com.ifeve.book.forkjoin.CopyOnWriteMap; /** * 黑名单服务 * * @author fangtengfei * */ public class BlackListServiceImpl { private static CopyOnWriteMap<String, Boolean> blackListMap = new CopyOnWriteMap<String, Boolean>( 1000); public static boolean isBlackList(String id) { return blackListMap.get(id) == null ? false : true; } public static void addBlackList(String id) { blackListMap.put(id, Boolean.TRUE); } /** * 批量添加黑名单 * * @param ids */ public static void addBlackList(Map<String,Boolean> ids) { blackListMap.putAll(ids); } }
代码很简单,可是使用CopyOnWriteMap须要注意两件事情: 数组
1. 减小扩容开销。根据实际须要,初始化CopyOnWriteMap的大小,避免写时CopyOnWriteMap扩容的开销。 安全
2. 使用批量添加。由于每次添加,容器每次都会进行复制,因此减小添加次数,能够减小容器的复制次数。如使用上面代码里的addBlackList方法。 多线程
CopyOnWrite容器有不少优势,可是同时也存在两个问题,即内存占用问题和数据一致性问题。因此在开发的时候须要注意一下。 并发
内存占用问题。由于CopyOnWrite的写时复制机制,因此在进行写操做的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会建立新对象添加到新容器里,而旧容器的对象还在使用,因此有两份对象内存)。若是这些对象占用的内存比较大,好比说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候颇有可能形成频繁的Yong GC和Full GC。以前咱们系统中使用了一个服务因为每晚使用CopyOnWrite机制更新大对象,形成了每晚15秒的Full GC,应用响应时间也随之变长。 优化
针对内存占用问题,能够经过压缩容器中的元素的方法来减小大对象的内存消耗,好比,若是元素全是10进制的数字,能够考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其余的并发容器,如ConcurrentHashMap。 网站
数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。因此若是你但愿写入的的数据,立刻能读到,请不要使用CopyOnWrite容器。 this
关于C++的STL中,曾经也有过Copy-On-Write的玩法,参见陈皓的《C++ STL String类中的Copy-On-Write》,后来,由于有不少线程安全上的事,就被去掉了。 spa