一、简介
CopyOnWriteArrayList是Java中的一种线程安全的ArrayList实现,它使用了"写时复制"技术来保证在并发情况下的线程安全。这种实现方式的思想是:当有修改操作时,不直接在原有的ArrayList上进行修改,而是先将原有的ArrayList复制一份,在新的ArrayList上进行修改操作,然后将原有的ArrayList指向新的ArrayList。这样就可以保证读操作和写操作互不影响。
二、 原理
CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。很多时候,我们的系统应对的都是读多写少的并发场景。
- 线程安全的,多线程环境下可以直接使用,无需加锁;
- 通过锁 + 数组拷贝 + volatile 关键字保证了线程安全;
- 每次数组操作,都会把数组拷贝一份出来,在新数组上进行操作,操作成功之后再赋值回去。
CopyOnWriteArrayList在对数组进行操作的时候分为四步:
1.加锁
2.从原数组拷贝一份新数组
3.在新数组上操作,并把原数组引用指向新数组
4.解锁
除了加锁之外,CopyOnWriteArrayList 的底层数组还被 volatile 关键字修饰,意思是一旦数组被修改,其它线程立马能够感知到。
三、优缺点
优点:
读操作(不加锁)性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。CopyOnWriteArrayList由于其"读写分离"的思想,遍历和修改操作分别作用在不同的list容器,所以在使用迭代器进行遍历时候,也就不会抛出ConcurrentModificationException异常
缺点:
一是内存占用较大,毕竟每次执行写操作都要将原容器拷贝一份。数据量大时,对内存压力较大,可能会引起频繁GC;
二是无法保证实时性,因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象
四、源码解析
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
//实例化ReentrantLock对象
final transient ReentrantLock lock = new ReentrantLock();
//定义一个私有变量的数组
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;
}
//将内部数组初始化为新建的空数组
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
@SuppressWarnings("unchecked")
//获取内部数组指定下标处的元素
private E get(Object[] a, int index) {
return (E) a[index];
}
//获取指定下标处的元素
public E get(int index) {
return get(getArray(), index);
}
//用新元素替换指定下标处的元素
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;
//通过Arrays.copyof创建新数组,并复制原数组的元素
Object[] newElements = Arrays.copyOf(elements, len);
//新添加的元素替换指定下标位置的元素
newElements[index] = element;
//调用setArray方法,将新数组设为内部数组
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
//把原数组引用指向新数组
setArray(elements);
}
//返回原数组指定下标处的元素
return oldValue;
} finally {
//默认释放锁
lock.unlock();
}
}
//将元素添加到列表末尾
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//复制原数组元素至新数组,新数组长度为len+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
//将新添加的元素放在末尾的位置
newElements[len] = e;
//把原数组引用指向新数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
//在指定下标处添加新元素,并将现有元素向右移动
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//判断需要添加的位置是否在合理范围内(0-elements.length)
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
//需要移动的元素个数
int numMoved = len - index;
//移动0个(末尾元素)
if (numMoved == 0)
//通过Arrays.copyof创建新数组,长度为原数组长度+1,并复制原数组的元素
newElements = Arrays.copyOf(elements, len + 1);
else {
//创建新数组,长度为原数组长度+1
newElements = new Object[len + 1];
//从原数组0处开始复制到新数组的0处,复制指定位置下标个元素
System.arraycopy(elements, 0, newElements, 0, index);
//从原数组index位置开始复制到新数组的指定位置+1处,复制移动的数字个元素
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//把新元素插入到新数组的指定下标处
newElements[index] = element;
//把原数组引用指向新数组
setArray(newElements);
} finally {
lock.unlock();
}
}
//删除指定下标处元素
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();
}
}
//删除指定范围的元素
void removeRange(int fromIndex, int toIndex) {
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//判断需要删除的范围是否合理
if (fromIndex < 0 || toIndex > len || toIndex < fromIndex)
throw new IndexOutOfBoundsException();
//剩余元素长度
int newlen = len - (toIndex - fromIndex);
//需要移动的元素个数
int numMoved = len - toIndex;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, newlen));
else {
Object[] newElements = new Object[newlen];
//复制剩余元素至新数组
System.arraycopy(elements, 0, newElements, 0, fromIndex);
System.arraycopy(elements, toIndex, newElements,
fromIndex, numMoved);
//把原数组引用指向新数组
setArray(newElements);
}
} finally {
lock.unlock();
}
}
//删除原数组中和集合c相等的所有元素
public boolean removeAll(Collection<?> c) {
//判断c是否为空
if (c == null) throw new NullPointerException();
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len != 0) {
// temp array holds those elements we know we want to keep
int newlen = 0;
Object[] temp = new Object[len];
//把原数组中的所有元素放在新数组中
for (int i = 0; i < len; ++i) {
Object element = elements[i];
//原数组中没有与c相同的元素,其他元素依次放入新数组
if (!c.contains(element))
temp[newlen++] = element;
}
//设置新数组的长度为删除后剩余元素的长度
if (newlen != len) {
setArray(Arrays.copyOf(temp, newlen));
return true;
}
}
//原数组长度为0,返回false
return false;
} finally {
//解锁
lock.unlock();
}
}
//删除所有元素
public void clear() {
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//原数组引用指向新创建长度为0的数组
setArray(new Object[0]);
} finally {
//解锁
lock.unlock();
}
}
//将集合c中所有元素添加到当前数组中
public boolean addAll(Collection<? extends E> c) {
//判断c是否为CopyOnWriteArrayList类型,如果是,通过geArray()方法获取c数组,否则通过c.toArray() 将集合转换为数组Object类型的数组cs
Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
if (cs.length == 0)
return false;
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//原数组为空且集合c的类型是Object类型
if (len == 0 && cs.getClass() == Object[].class)
//原数组引用指向cs
setArray(cs);
else {
//否则创建新数组并且复制原数组和cs数组
Object[] newElements = Arrays.copyOf(elements, len + cs.length);
System.arraycopy(cs, 0, newElements, len, cs.length);
//原数组引用指向新数组
setArray(newElements);
}
return true;
} finally {
//解锁
lock.unlock();
}
}
//按照给定的比较器c进行排序
public void sort(Comparator<? super E> c) {
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//复制新数组,将新数组转型为E<>泛型,然后按照比较器c进行排序
Object[] elements = getArray();
Object[] newElements = Arrays.copyOf(elements, elements.length);
@SuppressWarnings("unchecked") E[] es = (E[])newElements;
Arrays.sort(es, c);
//原数组引用指向新数组
setArray(newElements);
} finally {
//解锁
lock.unlock();
}
}
//判断当前实例是否与o相等
public boolean equals(Object o) {
//如果o和当前实例对象的哈希值相同,返回true,如果不是list类型,返回false
if (o == this)
return true;
if (!(o instanceof List))
return false;
//将o强制转换成List<>泛型类型
List<?> list = (List<?>)(o);
//使用迭代器遍历给定列表
Iterator<?> it = list.iterator();
Object[] elements = getArray();
int len = elements.length;
//如果没有可遍历的元素或者当前元素与遍历到的元素不相等,返回false
for (int i = 0; i < len; ++i)
if (!it.hasNext() || !eq(elements[i], it.next()))
return false;
//如果当前数组已经遍历完但o还有剩余元素,返回false,表示当前对象长度与o不同
if (it.hasNext())
return false;
return true;
}
//计算当前实例的哈希值
public int hashCode() {
int hashCode = 1;
//获取当前实例的数组和长度
Object[] elements = getArray();
int len = elements.length;
//遍历当前数组,获取每个元素,如果为空,哈希值为0,
for (int i = 0; i < len; ++i) {
Object obj = elements[i];
//31*当前的哈希值加当前的哈希值
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
}
}