概述
概述
CopyOnWrite(COW)是在写操作的时候copy当前数据,然后在写完数据之后设置成新的数据。适用于读多写少的并发场景。
CopyOnWrite 使用了 ReentrantLock(支持重入的独占锁) 来支持并发操作。ReentrantLock 是一种支持重入的独占锁,任意时刻只允许一个线程来获得锁。ReentrantLock 默认是非公平锁(即不按照进入等待队列的顺序唤醒线程)机制。
本质是一种延时策略,只有在真正需要复制的时候才复制,而不是提前复制好。
源码分析
- 添加与读取元素
final transient ReentrantLock lock = new ReentrantLock();
final transient volatile Object[] 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(); // 必须手动解锁
}
}
public boolean add (int index, E e){
final ReentrantLock lock = this.lock;
lock.lock();
try{
Object[] elements = getArray();
int len = elements.length;
if(index > len || index < ){
throw new IndexOutOfBoundsExcption("d--");
}
Obejct[] newElements;
int numMoved = len - index;
if(numMoved == 0){ // 如果插入尾部,则直接进行拷贝
newElements = Arrays.copyOf(elements, len+1);
} else { // 如果index位置上已经有了元素,则分两次拷贝
newElements = new Obejct[len+1];
// 将index之前的数据全部拷贝到新数组,不包括index
System.arrayCopy(elements, 0, newElements, 0, index);
// 将index之后拷贝到新数组,不包括index
System.arrayCopy(elements, idnex, newElements, index+1, numMoved);
}
newElements[index] = e;
setArray(newElements);
}finally{
lock.unlock();
}
}
// 读取的时候并没有加同步,因此允许多个线程进入读取
public E get(int index){
return get(getArray(), index);
}
pulbic
E get(Object[] array, int index){
return (E)array[index];
}
综上:在写入数据时使用ReentrantLock进行加锁,保证每一次写操作都是一个线程完成的,且每一次写操作都会先拷贝一份数据,然后再对新数组进行写操作;在读取数据的时候没有加锁,因此是允许多个线程进入访问的。所以,CopyOnWrite天生 适合读多写少的场景。
- 数组的遍历
private final Object[] snapshot; // 数组快照
private int cursor;
// 遍历的时候会先获得当前数组对象的一个拷贝,即生成一个快照,接着遍历操作在快照上进行。
public Iterator<E> iterator(){
return COWIterator<E>(getArray(),0);
}
public void testCOWIterator(){
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList();
list.add("xiaoming");
list.add("hanmeimei");
Iterator<String> iterator = list.iterator();
list.add("kangkang");
while(iterator.hasNext()){
syso(iterator.next());
}
// out xiaoming hanmeimei
}
综上:CopyOnWrite使用迭代器遍历时,我们只是获取当前list的一个快照,在对list获取迭代器后,再对list进行各种操作,是不会影响快照的遍历的,即每个线程都将获得当前数组的一个快照,所以迭代器不需要加锁就可以安全的实现遍历。
应用场景
应用于读多写少的并发场景。例如:白名单,黑名单,往往很长时间才会进行修改,大部分情况下我们只需要读数据。
总结
CopyOnWrite翻译来讲的话就是写时复制,就是我们往容器添加数据的时候不是直接添加,而是先将当前容器拷贝到新的容器中,然后往新的容器里面添加数据,最后在原容器的引用指向新的容器。因此,在并发读的情况下我们不需要加锁,因为当前的容器并不会被修改。
CopyOnWrite的优缺点:
- 优点:只在并发写的时候才会加锁,访问与读取不需要加锁,性能上比较高
- 缺点:
- 由于每次写数据时会复制整个数组,若容器对象占用的内存比较大时,容易发生GC。因此如果我们想用这个COW机制的话,数据不能太大,修改机会也比较少;
- 通过上面的源码分析,不难看出COW只能保证数据最终的一致性,不能保证数据的实时一致性。当我们碰到写入的数据需要立马读取到的话,就不要用CWO了。
最后说一句: CopyOnWriteArrayList 在修改的时候会复制整个数组,所以如果容器经常被修改或者这个数组本身就非常大的时候,是不建议使用的。反之,如果修改非常少,数组量又不大,并且对读性能要求比较高的场景,使用COW容器的效果就比较好了。
最后
以上就是贪玩酒窝为你收集整理的并发库 —— CopyOnWriteArrayList概述源码分析应用场景总结的全部内容,希望文章能够帮你解决并发库 —— CopyOnWriteArrayList概述源码分析应用场景总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复