CopyOnWrite,写复制容器,一种延时懒惰策略。
JDK5开始提供了两个写复制容器CopyOnWriteArrayList和CopyOnWriteArraySet。
CopyOnWrite即写复制容器。体现读写分离的思想,即add或者set元素的时候,copy一个容器用于写,以前的容器仍然可以读取,当添加完成元素之后,将复制的容器作为新的容器,废弃以前的容器GC掉。
CopyOnWriteArrayList核心源码
/**
* 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.lock();
try {
//获取数组
Object[] elements = getArray();
int len = elements.length;
//copy 得到新数组,len+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
//新数组add
newElements[len] = e;
//新数组替换旧数组,通过赋值的模式
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
CopyOnWriteArraySet,直接使用CopyOnWriteArrayList
/** @see CopyOnWriteArrayList
* @since 1.5
* @author Doug Lea
* @param <E> the type of elements held in this collection
*/
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
private static final long serialVersionUID = 5457747651344034263L;
private final CopyOnWriteArrayList<E> al;
public boolean add(E e) {
return al.addIfAbsent(e);
}
/**
* Appends the element, if not present.
*
* @param e element to be added to this list, if absent
* @return {@code true} if the element was added
*/
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
//indexOf来去重
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
/**
* A version of addIfAbsent using the strong hint that given
* recent snapshot does not contain e.
*/
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
可以看出写操作是加锁的,写是互斥的,读没有加锁,可以并发读取。
这种思想非常重要,在写操作比较少的时候,比如充当缓存的作用。
缺陷:
1. 占用内存空间
在写操作的时候复制容器就多占用一份内存空间,占用的堆内存,会在写操作前分配初始化内存,写完后GC回收,可能会造成频繁的Minor GC、Full GC。
2. 一致性问题
延时懒惰策略,只能保证最终一致性。