写时复制,简称COW,基本思想是读的时候多线程之间共享内容,当一条线程修改内容时,会进行copy资源进行修改形成新的内容,即读写分离的思想,读和写不同的容器。JUC中提供了CopyOnWriteArrayList和CopyOnWriteArraySet。
其好处在于对COW容器的并发读不需要加锁 CopyOnWriteArrayList的add方法,调用的时候是需要加锁的,否则多线程写的时候会copy出多个副本
/**
* Appends the specified element to the end of this list. * * @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/public boolean add(E e) {
final ReentrantLock lock = this.lock;
//lock操作放在try catch块外面,这样即使获取锁异常(比如自定义的时候)也不会无故释放锁
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();
}
}
//读就不需要加锁了
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
模仿COW思想实现COWMap
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;
}
}
}
COW容器缺点
因为写时复制的机制,在进行写操作的时候会有两个对象在内存中(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存),当对象的大小比较大的时候,很有可能造成频繁的GC 此外COW容器只能保证数据的最终一致性,不能保证实时一致性