CopyOnWriteArrayList是一个线程安全集合,原理简单说就是:在保证线程安全的前提下,牺牲掉写操做的效率来保证读操做的高效。所谓CopyOnWrite就是经过复制的方式来完成对数据的修改,在进行修改的时候,复制一个新数组,在新数组上面进行修改操做,这样就保证了不改变老数组,也就没有一写多读数据不一致的问题了。java
具体的实现来看源码,JDK 8。数据库
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 复制代码
在定义上和ArrayList大差不差,不过多解释,有兴趣能够看以前关于ArrayList的文章。数组
属性缓存
一个是Lock,另外一个是一个对象数组。安全
/** The lock protecting all mutators */
//一把锁
transient final ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
//一个对象数组,只从方法getArray/setArray处接受值
//volatile后面会有专门的文章来讲明
private volatile transient Object[] array;
复制代码
CopyOnWriteArrayList的初始化容量是0,分为这样的几个步骤。并发
//在无参构造方法中会调用setArray方法,参数是一个空的对象数组,而后经过setArray把这个空的数组赋值给属性array
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
final void setArray(Object[] a) {
array = a;
}
复制代码
须要说明的是另外一个有参构造方法,参数能够是一个集合dom
//按照集合的迭代器返回的顺序建立一个包含指定集合元素的列表
public CopyOnWriteArrayList(Collection<? extends E> c) {
//将集合转为数组
Object[] elements = c.toArray();
//elements不可以是一个空的对象数组 为何要if这样一个条件嘞 由于属性中须要赋值的是一个对象数组 因此若是if成立执行的就是把原数组变为一个对象数组 若是自己就是对象数组也就不用转了
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
//赋值给属性
setArray(elements);
}
复制代码
添加一个新元素到list的尾部。高并发
public boolean add(E e) {
//锁 1.5新版本的锁 已经不用synchronized了
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//getArray获取属性值 就是老数组
Object[] elements = getArray();
int len = elements.length;
//这里是重点 在这里 复制老数组获得了一个长度+1的新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//添加元素
newElements[len] = e;
//用新数组取代老数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
复制代码
从add方法中咱们能够看到所谓的CopyOnWrite是如何实现的,在须要修改的时候,复制一个新数组,在新数组上修改,修改结束取代老数组,这样保证了修改操做不影响老数组的正常读取,另修改操做是加锁的,也就是说没有了线程不安全的问题。this
和ArrayList相比较,效率比较低,只添加一个元素的状况下(初始容量均为0),用时是ArrayList的5倍左右,可是随着CopyOnWriteArrayList中元素的增长,CopyOnWriteArrayList的修改代价将愈来愈昂贵。spa
除了添加其余的修改操做也都是这样的套路,不作过多解释,如remove,也是加锁,复制新数组。
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();
}
}
复制代码
#####get
public E get(int index) {
return get(getArray(), index);
}
//按照下标获取数组中对应的元素
private E get(Object[] a, int index) {
return (E) a[index];
}
复制代码
读取的方法就很简单了,按照下标获取对应的元素。
我不能保证每个地方都是对的,可是能够保证每一句话,每一行代码都是通过推敲和斟酌的。但愿每一篇文章背后都是本身追求纯粹技术人生的态度。
永远相信美好的事情即将发生。