本文详细介绍CopyOnWriteArrayList,从概述、原理、使用、源码、缺点等方面进行说明
背景
简要介绍下CopyOnWriteArrayList产生的背景
1.Vedtor 和SynchronizedList的锁力度比较大,基本上可以认为都是加锁在方法层面,并发度降低。(只有一把锁)
2.CopyOnWriteArrayList降低了锁的力度,并且在迭代时是可以编辑的。
3.CopyOnWrite容器中其他的实现,如:CopyOnWriteArraySet
适用场景
1.读操作需要足够快,写操作慢一点没啥关系;比如系统中黑名单、监听器(监听很多的时间,读的场景多)
读写规则
我们都知道读写锁的工作原理,即为多读一写。
为了将读的性能提高到最大,CopyOnWrite不再加读锁,只提供写写的互斥操作。
实例代码
首先我们演示ArrayList在迭代的时候进行了新增或删除,这里我们给出结论:ArrayList在迭代的时候不允许修改,见下代码
package com.yang.concurrent; import java.util.ArrayList; import java.util.Iterator; public class ArrayListIteratorDemo { public static void main(String[] args) { ArrayList<Integer> list=new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); Iterator<Integer> iterator=list.iterator(); while (iterator.hasNext()){ Integer item=iterator.next(); System.out.println(item); if (item==2){ list.remove(2); } } } }
运行结果如下:不允许对迭代过程中对List进行操作。
我们用CopyOnWriteArrayList在迭代的时候进行下删除获取新增。
package com.yang.concurrent; import java.util.ArrayList; import java.util.Iterator; import java.util.concurrent.CopyOnWriteArrayList; public class CopyOnWriteArrayListIteratorDemo { public static void main(String[] args) { CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Integer item = iterator.next(); System.out.println(item); if (item == 2) { list.remove(2); } System.out.println(list); } } }
运行结果如下,允许在迭代的过程中进行对List操作,不影响迭代。如下图。
原理
我们看下CopyOnWrite的原理。我们用的时候,重新拷贝一份新的,用完之后,将指针指向原来的地址。
创建新的副本,读写分离
以下代码演示迭代过程中数据过期的问题,见下代码。
package com.yang.concurrent; import java.util.Iterator; import java.util.concurrent.CopyOnWriteArrayList; /** *本实例演示迭代过程中数据过期的问题 */ public class CopyOnWriteArrayListDemo2 { public static void main(String[] args) { CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(new Integer[]{1, 2, 3}); //迭代的数据取决了迭代器的生成时间 Iterator<Integer> iterator1 = list.iterator(); list.add(4); Iterator<Integer> iterator2 = list.iterator(); iterator1.forEachRemaining(System.out::println); iterator2.forEachRemaining(System.out::println); } }
CopyOnWriteArrayList缺点
1.不能保证实时数据一致性
2.内存占用问题,写操作时复制的,会有内存开销
源码分析
我们从初始化、锁、写操作、读操作进行分析。
初始化代码如下所示:
观察下使用的Lock锁。
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; /** The lock protecting all mutators */ final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array; .........
读操作的实现代码如下,我们发现未加锁,则读操作可能获取的数据不是最新的。
@SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; }
我们重点分析下写操作,我们发现写的时候,是互斥的,需要获取ReentrantLock,对原来的数组进行拷贝,待写入完成后,重新改变引用地址。如下所示:
public E set(int index, E element) { //获取锁 final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); E oldValue = get(elements, index); if (oldValue != element) { int len = elements.length; //数组拷贝 Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics setArray(elements); } return oldValue; } finally { lock.unlock(); } }