一、 CopyOnWriteArrayList介绍
- CopyOnWriteArrayList,写数组的拷贝,支持高效率并发且是线程安全的,读操作无锁的ArrayList。所有可变操作都是通过对底层数组进行一次新的复制来实现。
- CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。它不存在扩容的概念,每次写操作都要复制一个副本,在副本的基础上修改后改变Array引用。CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差
- 在迭代器上进行的元素更改操作(remove、set和add)不受支持。这些方法将抛出UnsupportedOperationException。
它相当于线程安全的ArrayList。和ArrayList一样,它是个可变数组;但是和ArrayList不同的时,它具有以下特性:
- 它最适合于具有以下特征的应用程序:List 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。
- 它是线程安全的。
- 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。
- 迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等操作。
- 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。
二、 CopyOnWriteArrayList原理
- CopyOnWriteArrayList实现了List接口,因此它是一个队列。
- CopyOnWriteArrayList包含了成员lock。每一个CopyOnWriteArrayList都和一个监视器锁lock绑定,通过lock,实现了对CopyOnWriteArrayList的互斥访问。
- CopyOnWriteArrayList包含了成员array数组,这说明CopyOnWriteArrayList本质上通过数组实现的。
- 4.CopyOnWriteArrayList的“动态数组”机制 -- 它内部有个“volatile数组”(array)来保持数据。在“添加/修改/删除”数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile数组”。这就是它叫做CopyOnWriteArrayList的原因!CopyOnWriteArrayList就是通过这种方式实现的动态数组;不过正由于它在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyOnWriteArrayList效率很 低;但是单单只是进行遍历查找的话,效率比较高。
- 5.CopyOnWriteArrayList的“线程安全”机制 -- 是通过volatile和监视器锁Synchrnoized来实现的。
- 6.CopyOnWriteArrayList是通过“volatile数组”来保存数据的。一个线程读取volatile数组时,总能看到其它线程对该volatile变量最后的写入;就这样,通过volatile提供了“读取到的数据总是最新的”这个机制的 保证。
- 7.CopyOnWriteArrayList通过监视器锁Synchrnoized来保护数据。在“添加/修改/删除”数据时,会先“获取监视器锁”,再修改完毕之后,先将数据更新到“volatile数组”中,然后再“释放互斥锁”;这样,就达到了保护数据的目的。
三、 CopyOnWriteArrayList 属性介绍
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
/**
* 监视器锁
*/
final transient Object lock = new Object();
/** 一个缓存数组,使用volatile修饰。 */
private transient volatile Object[] array;
/**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
*/
final Object[] getArray() {
return array;
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
}
四、 构造器
- 无参构造,默认创建的是一个长度为0的数组
//无参构造,默认创建的是一个长度为0的数组
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
- 参数为Collection的构造方法
//参数为Collection的构造方法
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] es;
//判断传递的是否就是一个CopyOnWriteArrayList集合
if (c.getClass() == CopyOnWriteArrayList.class)
//如果是,直接调用getArray方法,获得传入集合的array然后赋值给elements
es = ((CopyOnWriteArrayList<?>)c).getArray();
else {
//先将传入的集合转变为数组形式
es = c.toArray();
//c.toArray()可能不会正确地返回一个 Object[]数组,那么使用Arrays.copyOf()方法
if (c.getClass() != java.util.ArrayList.class)
es = Arrays.copyOf(es, es.length, Object[].class);
}
//直接调用setArray方法设置array属性
setArray(es);
}
- 创建一个包含给定数组副本的list
//创建一个包含给定数组副本的list
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
说明:
- setArray()的作用是给array赋值;其中,array是volatile transient Object[]类型,即array是“volatile数组”。
- 关于volatile关键字,我们知道“volatile能让变量变得可见”,即对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。正在由于这种特性,每次更新了“volatile数组”之后,其它线程都能看到对它所做的更新。
- 关于transient关键字,它是在序列化中才起作用,transient变量不会被自动序列化。
上面介绍的是CopyOnWriteList的初始化,三个构造方法都比较易懂
五、添加add方法
public boolean add(E e) {
//加锁
synchronized (lock) {
//获得list底层的数组array
Object[] es = getArray();
//获得数组长度
int len = es.length;
//拷贝到新数组,新数组长度为len+1
es = Arrays.copyOf(es, len + 1);
//给新数组末尾元素赋值
es[len] = e;
//用新的数组替换掉原来的数组
setArray(es);
return true;
}
}
- 1、加锁(synchronized )。保证此时只有一个线程进入
- 2、获取原来的数组以及长度
- 3、复制新数据,并且长度+1
- 4、设置新数据(array使用volatile修饰,所以其他线程可见)
- 5、返回
六、获取元素 get(int index)
使用get(i)可以获取指定位置i的元素,当然如果元素不存在就会抛出数组越界异常。
public E get(int index) {
return elementAt(getArray(), index);
}
static <E> E elementAt(Object[] a, int index) {
return (E) a[index];
}
六、remove(int index)
public E remove(int index) {
//加锁
synchronized (lock) {
//获取原数组
Object[] es = getArray();
//获取原数组长度
int len = es.length;
//获取原数组index处的值
E oldValue = elementAt(es, index);
//因为数组删除元素需要移动,所以这里就是计算需要移动的个数
int numMoved = len - index - 1;
Object[] newElements;
if (numMoved == 0)
//计算的numMoved=0,表示要删除的是最后一个元素
//那么旧直接将原数组的前len-1个复制到新数组中,替换旧数组即可
newElements = Arrays.copyOf(es, len - 1);
//要删除的不是最后一个元素
else {
//创建一个长度为len-1的数组
newElements = new Object[len - 1];
//将原数组中index之前的元素复制到新数组
System.arraycopy(es, 0, newElements, 0, index);
//将原数组中index之后的元素复制到新数组
System.arraycopy(es, index + 1, newElements, index,
numMoved);
}
//用新数组替换原数组
setArray(newElements);
return oldValue;
}
}
remove(int index)的作用就是将”volatile数组“中第index个元素删除。它的实现方式是,如果被删除的是最后一个元素,则直接通过Arrays.copyOf()进行处理,而不需要新建数组。否则,新建数组,然后将”volatile数组中被删除元素之外的其它元素“拷贝到新数组中;最后,将新数组赋值给”volatile数组“。和add(E e)一样,remove(int index)也是”在操作之前,获取独占锁;操作完成之后,释放独占是“;并且”在操作完成时,会通过将数据更新到volatile数组中“。
七、修改元素
修改也是属于写 ,所以需要获取lock,下面就是set方法的实现
public E set(int index, E element) {
//加锁
synchronized (lock) {
//获取数组array
Object[] es = getArray();
//获取index位置的元素
E oldValue = elementAt(es, index);
// 要修改的值和原值不相等
if (oldValue != element) {
//克隆一个新数组
es = es.clone();
//替换元素
es[index] = element;
}
// Ensure volatile write semantics even when oldvalue == element
// 即使在oldvalue==元素的情况下也要确保易失性写入语义
setArray(es);
return oldValue;
}
}
八、 迭代器
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
- 使用了COWIterator遍历
static final class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array */
//array的快照版本
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
//后续调用next返回的元素索引(数组下标)
private int cursor;
//构造器
COWIterator(Object[] es, int initialCursor) {
cursor = initialCursor;
snapshot = es;
}
//变量是否结束:下标小于数组长度
public boolean hasNext() {
return cursor < snapshot.length;
}
//是否有前驱元素
public boolean hasPrevious() {
return cursor > 0;
}
//获取元素
//hasNext()返回true,直接通过cursor记录的下标获取值
//hasNext()返回false,抛出异常
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
@SuppressWarnings("unchecked")
public E previous() {
if (! hasPrevious())
throw new NoSuchElementException();
return (E) snapshot[--cursor];
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code remove}
* is not supported by this iterator.
*/
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code set}
* is not supported by this iterator.
*/
public void set(E e) {
throw new UnsupportedOperationException();
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code add}
* is not supported by this iterator.
*/
public void add(E e) {
throw new UnsupportedOperationException();
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int size = snapshot.length;
int i = cursor;
cursor = size;
for (; i < size; i++)
action.accept(elementAt(snapshot, i));
}
}
说明:COWIterator不支持修改元素的操作。例如,对于remove(),set(),add()等操作,COWIterator都会抛出异常!
另外,需要提到的一点是,CopyOnWriteArrayList返回迭代器不会抛出ConcurrentModificationException异常,即它不是fail-fast机制的!
参考:
https://blog.csdn.net/GoSaint/article/details/115234349