Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。
JDK1.5开始,JUC (java.util.concurrent)包下有着这么两个类,CopyOnWriteArrayList 和 CopyOnWriteArraySet。直译过来就是在写操作的时候复制。这体现了读写分离的思想。
CopyOnWriteArrayList的实现原理
add()的实现:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E 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();
}
}
读的时候不加锁,但是如果读的时候有很多个线程正在向CopyOnWriteArrayList中添加数据,读的时候就会读到旧的数据。
public E get(int index) {
return get(getArray(), index);
}
CopyOnWriteMap
JDK中并没有提供CopyOnWriteMap,我们可以参考CopyOnWriteArrayList来实现一个,基本代码如下:
优缺点
- 在写操作的线程,会将数组复制出来一份进行操作。而原本的数组不会做改变。
- 读线程则不会受到影响,但是可能读到的是一个过期的数据。
- 每次添加都要复制,内存占用较大,可能造成频繁的Young GC和Full GC。性能消耗较大。
只能保证最终的一致性,不能保证实时的一致性。
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;//volatile关键字修饰
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;
}
}
}
应用场景
读多写少的情况下适用:比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索。
package com.ifeve.book;
import java.util.Map;
import com.ifeve.book.forkjoin.CopyOnWriteMap;
/**
* 黑名单服务
*
* @author fangtengfei
*
*/
public class BlackListServiceImpl {
//初始化CopyOnWriteMap的大小,避免写时CopyOnWriteMap扩容的开销
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的大小,避免写时CopyOnWriteMap扩容的开销。
- 使用批量添加。因为每次添加,容器每次都会进行复制,所以减少添加次数,可以减少容器的复制次数。如使用上面代码里的addBlackList方法。