更多Java容器源码分析可以参考:Java容器源码分析系列(持续更新中!)
(一)、概述
- CopyOnWriteArrayList的特点主要是实现了读写分离,读操作时不加锁,写操作才进行加锁,防止并发时写入导致数据丢失
- 在进行写操作的时候,不是在原数组上直接修改,而是创建一个新的数组,对原数组进行复制修改等操作后,再将指针指向新的数组。
(二)、适用场景
- CopyOnWriteArrayList主要使用于读多写少的应用场景,这会大大提高读的性能
- 缺点:
- List item会占用较多内存,因为在写操作的时候,都需要创建一个新的数
- 可能会出现数据不一致的情况,读操作可能读到过时的数据,因为写操作不在原数组上进行,可能还未来得及指向新数组。
(二)、类名
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
- CopyOnWriteArrayList实现了List接口、RandomAccess接口、Clonable接口以及Serializable接口
- List接口:主要提供一些增删改查的方法
- RandomAccess接口:这是一个空的接口,实现了这个接口,就可以实现随机访问
- Cloneable接口:实现了这个接口,能够实现克隆功能。
- Serializable接口:可以实现序列化功能。
(三)、成员变量
/**
* 重入锁ReentrantLock,同时也是线程独占锁,用来保证线程安全
*/
final transient ReentrantLock lock = new ReentrantLock();
/**
* 用来存放数组对象
*/
private transient volatile Object[] array;
- lock:用来在更新修改操作时保证线程独占
- array:用来存放真实的数据对象
(四)、构造方法
/**
* 获得数组对象
*/
final Object[] getArray() {
return array;
}
/**
* 设置数组对象
*/
final void setArray(Object[] a) {
array = a;
}
/**
* 无参构造方法
*/
public CopyOnWriteArrayList() {
//创建一个长度为0的空数组
setArray(new Object[0]);
}
/**
* 参数为集合对象的构造方法
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
//查看参数c是否本身已经是CopyOnWriteArrayList
if (c.getClass() == CopyOnWriteArrayList.class)
//如果本身就是,那就直接获取CopyOnWriteArrayList中的数组对象
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
//否则,先将参数c转换为数组对象
elements = c.toArray();
// 查看element中的元素是否是Object类型的
if (elements.getClass() != Object[].class)
//如果不是,就重新创建一个Object类型的数组,将元素复制进去
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
//通过setArray方法设置数组对象
setArray(elements);
}
/**
* 参数为数组对象的有参构造方法
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
- 首先,构造方法中基本都设计了两个比较简单的方法:setArray()和getArray()。两个方法的实现都较为简单,源码也已给出。
- CopyOnWriteArrayList():无参构造方法。方法中创建了一个长度为0的空数组
- CopyOnWriteArrayList(Collection<? extends E> c):参数为集合对象的构造方法,该方法可以将c对象中的数据复制到this对象中
- CopyOnWriteArrayList(E[] toCopyIn):参数为数组的有参构造方法,可以将数组的元素复制到this对象中。
(五)、set方法
/**
* set方法,可以设置数组对象中的值
*/
public E set(int index, E element) {
//获得锁对象
final ReentrantLock lock = this.lock;
//锁住
lock.lock();
try {
//获取数组对象
Object[] elements = getArray();
//获取elements对象在index位置上的元素
E oldValue = get(elements, index);
//如果旧值不等于新值
if (oldValue != element) {
//获取数组对象的长度
int len = elements.length;
//复制原来的数组对象
Object[] newElements = Arrays.copyOf(elements, len);
//将新数组对象中index位置上的值设置为新值
newElements[index] = element;
//将新数组对象写会CopyOnWriteArrayList中
setArray(newElements);
} else {
// 否则无需修改原来对的数组对象
setArray(elements);
}
//返回更新前的值
return oldValue;
} finally {
//解锁
lock.unlock();
}
}
- 首先获得锁对象,将方法锁定
- 第二步,查看要更新的值和原来的旧值是否一样
- 如果一样就选择不更新
- 如果不一样的话,先将原来的数组拷贝一份出来,成为新数组
- 然后将新数组上index位置上的元素设置为新值element
- 然后将新数组写回
- 最后,记得解除锁定
(六)、add方法
/**
* 在末尾添加上元素的add方法
*/
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);
//在末尾添加上元素e
newElements[len] = e;
//将新数组重新设置到this对象中
setArray(newElements);
//返回true
return true;
} finally {
//解锁
lock.unlock();
}
}
/**
* 在指定位置上添加元素的add方法
*/
public void add(int index, E element) {
//首先,获得锁对象
final ReentrantLock lock = this.lock;
//锁住
lock.lock();
try {
//获得原数组对象
Object[] elements = getArray();
//获取原数组的长度
int len = elements.length;
//查看插入位置是否越界
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
//创建新数组对象
Object[] newElements;
//计算要移动的位置
int numMoved = len - index;
//如果是在末尾位置插入的话,就创建一个长度为len+1的新数组,将原数组中的数据复制进去
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
//否则,同样创建一个长度为len+1的数组
newElements = new Object[len + 1];
//从原数组的0到index-1位置上的元素复制到新数组中
System.arraycopy(elements, 0, newElements, 0, index);
//将原数组的index位置到末尾上的元素复制到新数组中
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//在index位置上设置元素为element
newElements[index] = element;
//设置回this对象中
setArray(newElements);
} finally {
//解锁
lock.unlock();
}
}
- 两个add方法的实现都差不多,这里选择第二个方法进行讲解
- add(int index, E element)方法:是在指定位置上添加元素的
- 首先获得锁对象,并且锁住这个方法
- 获得原数组的对象,计算长度,查看想插入的位置index是否合法
- 然后计算插入该元素需要移动元素的个数
- 如果numMoved=0,那说明想要在数组的末尾插入元素,那么就直接将原数组的元素复制到新数组中
- 如果numMoved>0,就将index位置空出来,其他元素全部移动到新数组中
- 然后在新数组中添加上index位置上的值
- 最后,要记得解锁。
(七)、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)
//直接将0~len-1上的元素拷贝到新数组中,并将指向新数组
setArray(Arrays.copyOf(elements, len - 1));
else {
//创建一个长度为len-1的数组对象
Object[] newElements = new Object[len - 1];
//将原数组中0~index-1的元素复制到新数组中
System.arraycopy(elements, 0, newElements, 0, index);
//将原数组中index+1到末尾的元素复制到新数组中
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
//指向新的数组对象
setArray(newElements);
}
//返回旧值
return oldValue;
} finally {
//解锁
lock.unlock();
}
}
/**
* 根据对象进行删除
*/
public boolean remove(Object o) {
//获取原数组对象
Object[] snapshot = getArray();
//计算o对象在原数组中的位置
int index = indexOf(o, snapshot, 0, snapshot.length);
//三元表达式,如果index小于0表示对象不存在,大于0则删除对象
return (index < 0) ? false : remove(o, snapshot, index);
}
/**
*
*/
private boolean remove(Object o, Object[] snapshot, int index) {
//获得锁对象
final ReentrantLock lock = this.lock;
//锁住
lock.lock();
try {
//获得原数组对象
Object[] current = getArray();
//获得原数组的长度
int len = current.length;
//如果当前的数组对象和传入的snapshot数组对象不一样,说明被其他线程修改了
if (snapshot != current) findIndex: {
//获取两者的最小长度
int prefix = Math.min(index, len);
//遍历两者共有的长度
for (int i = 0; i < prefix; i++) {
//如果两个数组在i位置上的元素不一样,且o对象等于当前数组上i位置上的元素
if (current[i] != snapshot[i] && eq(o, current[i])) {
//要删除的位置就是i位置
index = i;
//结束循环
break findIndex;
}
}
//查看index位置是否越界
if (index >= len)
return false;
if (current[index] == o)
break findIndex;
//前面查找失败,就继续从index往后查找
index = indexOf(o, current, index, len);
if (index < 0)
return false;
}
//创建一个长度为len-1的新数组对象
Object[] newElements = new Object[len - 1];
//将原数组中除了index位置上的元素,全部搬迁到新数组中
System.arraycopy(current, 0, newElements, 0, index);
System.arraycopy(current, index + 1,
newElements, index,
len - index - 1);
//指向新数组
setArray(newElements);
//返回true
return true;
} finally {
//解锁
lock.unlock();
}
}
- remove方法的基本实现其实也差不多,这里就选择第二个和第三个方法进行讲解。
- 第二个方法传入的是一个Object对象,他会在这个方法里面查看原数组中是否含有这个o对象,如果含有就调用第三个remove方法。因为这里只是读取,所以不用加锁
- 第三个remove方法中,参数包含了第二个方法传来的数组snapshot。
- 首先获得锁对象,锁住方法。
- 然后判断snapshot对象是否和当前数组对象current相同,以此来判断是否在这间隙被修改过
- 然后注意遍历current数组中的元素,看是否含有o对象
- 接着遍历完后,创建一个长度为len-1的新数组对象,将原数组中的元素搬到新数组中。
- 最后指向新数组,释放锁。
(八)、addIfAbsent方法
/**
* 还有一个参数e的addIfAbsent方法
*/
public boolean addIfAbsent(E e) {
//获得原数组对象
Object[] snapshot = getArray();
//三元表达式,如果原数组中含有这个元素了,就直接返回false
//如果不含有,就调用方法进行添加
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
/**
* 两个参数对的addIfAbsent方法
*/
private boolean addIfAbsent(E e, Object[] snapshot) {
//获得锁对象
final ReentrantLock lock = this.lock;
//锁住
lock.lock();
try {
//获得原数组对象
Object[] current = getArray();
//获得原数组对象的长度
int len = current.length;
//如果当前原数组对象和传入的snapshot数组对象不一致,说明被修改了
if (snapshot != current) {
// 选择两者的最小值
int common = Math.min(snapshot.length, len);
//遍历两者的最小长度
for (int i = 0; i < common; i++)
//如果两者元素不一样,且当前原数组中含有e对象就直接返回false
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
//从common位置开始遍历完所有元素,如果current数组中存在就返回false
if (indexOf(e, current, common, len) >= 0)
return false;
}
//创建一个长度为len+1的数组对象,并将原数组的元素复制到新数组中
Object[] newElements = Arrays.copyOf(current, len + 1);
//设置最末尾的元素为e
newElements[len] = e;
//指向新数组
setArray(newElements);
//返回true
return true;
} finally {
//解锁
lock.unlock();
}
}
- addIfAbsent方法主要是用来判断数组中是否含有对象e,如果不含有,就添加到数组中
- 首先是addIfAbsent(E e)这个方法,使用indexOf函数查看是否含有e对象,如果不含有就调用方法进行添加。
- addIfAbsent(E e, Object[] snapshot)方法接收了第一个方法传来的原数组作为参数
- 首先获得锁对象,锁住方法
- 获得原数组对象,并获取原数组对象的长度
- 如果当前原数组对象和传入的snapshot数组对象不一致,说明被修改了,那就遍历current数组中是否含有e对象
- 接着遍历完成后,创建一个长度为len+1的数组对象,并将原数组的元素复制到新数组中,设置最末未的元素为e
- 最后指向新数组,释放锁