原文连接《CopyOnWrite的实现机制》
1、什么是CopyOnWrite
和单词描述的一样,他的实现就是写时复制, 在往集合中添加数据的时候,先拷贝存储的数组,然后添加元素到拷贝好的数组中,然后用现在的数组去替换成员变量的数组(就是get等读取操作读取的数组)。这个机制和读写锁是一样的,但是比读写锁有改进的地方,那就是读取的时候可以写入的 ,这样省去了读写之间的竞争,看了这个过程,你也发现了问题,同时写入的时候怎么办呢,当然果断还是加锁。 这体现了读写分离的思想:在写操作的线程,会将数组复制出来一份进行操作。而原本的数组不会做改变。读线程则不会受到影响,但是可能读到的是一个过期的数据。
2、CopyOnWrite都有哪些具体的实现类
在java中实现CopyOnWrite相关功能的类主要为:CopyOnWriteArrayList、CopyOnWriteArraySet、CopyOnWriteHashMap。内部的具体都是十分相似的,在涉及到集合修改的地方对集合执行拷贝动作,在新拷贝的集合上进行修改操作。
3、CopyOnWrite流程分析
这里以CopyOnWriteArrayList进行操作流程分析。
集合的读取操作:
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
直接获取当前维护的集合引用,通过引用直接返回指定下标对应的元素 集合的修改操作:
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();
}
}
添加新元素时,通过系统拷贝函数,直接将现有的集合拷贝至另一个新集合中,新集合的new.size = old.size+1,将新增的元素添加到新集合的空位中。
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
在删除操作时,除了移除当前指定的元素之外,还需要调整被删除之后元素的位置,即要保证在整个list中,所有的元素都是紧密挨着的,元素之间不存在空位。其中在代码中通过size-index-1来计算集合中需要进行移位操作的元素个数。在代码中的具体实现表现为两次拷贝,以待移除的元素索引为分割,首先拷贝[0,index),在拷贝(index,size),分两次拷贝动作将元素全部拷贝到一个全新的集合中。至此完成集合的元素修改。
4、CopyOnWrite的使用场景
由于在元素集合读取的时候,并没有相关的加锁操作,就读操作而言,不会影响集合的操作性能,但是由于修改操作会导致新集合的创建以及老集合的元素拷贝,比较消耗性能。而且由于修改操作在全操作中都会进行上锁来保证线程间的同步安全。所以CopyOnWrite操作更多的适合小集合并且读多写少的情况。这里的修改拷贝,主要是为了读写分离,线程间的同步安全通过在方法中使用ReentrantLock来对代码段进行加锁来保证。