文章目录
- (一)、概述
- (二)、适用场景
- (二)、类名
- (三)、成员变量
- (四)、构造方法
- (五)、set方法
- (六)、add方法
- (七)、remove方法
- (八)、addIfAbsent方法
更多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接口:可以实现序列化功能。
(三)、成员变量
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() {
setArray(new Object[0]);
}
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
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方法
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 {
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
- 首先获得锁对象,将方法锁定
- 第二步,查看要更新的值和原来的旧值是否一样
- 如果一样就选择不更新
- 如果不一样的话,先将原来的数组拷贝一份出来,成为新数组
- 然后将新数组上index位置上的元素设置为新值element
- 然后将新数组写回
- 最后,记得解除锁定
(六)、add方法
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
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;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
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)
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();
}
}
public boolean remove(Object o) {
Object[] snapshot = getArray();
int index = indexOf(o, snapshot, 0, snapshot.length);
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;
if (snapshot != current) findIndex: {
int prefix = Math.min(index, len);
for (int i = 0; i < prefix; i++) {
if (current[i] != snapshot[i] && eq(o, current[i])) {
index = i;
break findIndex;
}
}
if (index >= len)
return false;
if (current[index] == o)
break findIndex;
index = indexOf(o, current, index, len);
if (index < 0)
return false;
}
Object[] newElements = new Object[len - 1];
System.arraycopy(current, 0, newElements, 0, index);
System.arraycopy(current, index + 1,
newElements, index,
len - index - 1);
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
- remove方法的基本实现其实也差不多,这里就选择第二个和第三个方法进行讲解。
- 第二个方法传入的是一个Object对象,他会在这个方法里面查看原数组中是否含有这个o对象,如果含有就调用第三个remove方法。因为这里只是读取,所以不用加锁
- 第三个remove方法中,参数包含了第二个方法传来的数组snapshot。
- 首先获得锁对象,锁住方法。
- 然后判断snapshot对象是否和当前数组对象current相同,以此来判断是否在这间隙被修改过
- 然后注意遍历current数组中的元素,看是否含有o对象
- 接着遍历完后,创建一个长度为len-1的新数组对象,将原数组中的元素搬到新数组中。
- 最后指向新数组,释放锁。
(八)、addIfAbsent方法
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
- addIfAbsent方法主要是用来判断数组中是否含有对象e,如果不含有,就添加到数组中
- 首先是addIfAbsent(E e)这个方法,使用indexOf函数查看是否含有e对象,如果不含有就调用方法进行添加。
- addIfAbsent(E e, Object[] snapshot)方法接收了第一个方法传来的原数组作为参数
- 首先获得锁对象,锁住方法
- 获得原数组对象,并获取原数组对象的长度
- 如果当前原数组对象和传入的snapshot数组对象不一致,说明被修改了,那就遍历current数组中是否含有e对象
- 接着遍历完成后,创建一个长度为len+1的数组对象,并将原数组的元素复制到新数组中,设置最末未的元素为e
- 最后指向新数组,释放锁