浅析JDK1.7 CopyOnWriteArrayList源码。
写在开篇
CopyOnWriteArrayList使用了一种写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。
CopyOnWriteArrayList采用了读写分离的思想,读和写不同的容器。如果写操作未完成或者引用还未指向新数组,那么从原数组读取数据;如果写操作完成,并且引用已经指向了新的数组,那么从新数组中读取数据。所以CopyOnWriteArrayList只能保证数据的最终一致性,不能保证数据的实时一致性。
类的继承关系
CopyOnWriteArrayList跟ArrayList一样实现了List,RandomAccess, Cloneable,Serializable接口,但是没有继承AbstractList。1
2public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
成员变量
1 | /** 可重入锁 */ |
构造方法
CopyOnWriteArrayList有三个构造方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/**
* 创建一个空数组
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* 以一个实现了Collection接口的集合类,来创建array
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements = c.toArray();
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
setArray(elements);
}
/**
* 以一个CopyOnWriteArrayList的集合类,来创建array
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
List接口的实现
add()
和ArrayList一样的,CopyOnWriteArrayList有两种插入元素的方法。先看第一种,默认在数组尾部插入元素。1
2
3
4
5
6
7
8
9
10
11
12
13
14public 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); // 将array复制到一个扩容了的新数组
newElements[len] = e; // 插入新值
setArray(newElements); // 将array引用指向新数组
return true;
} finally {
lock.unlock(); // 释放锁
}
}
另一种是在指定下标插入元素。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public 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];
// 拷贝旧数组0到index处的到新数组0到index
System.arraycopy(elements, 0, newElements, 0, index);
// 拷贝旧数组index到最后的数组到新数组index+1到最后
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element; // 插入新元素
setArray(newElements); // 将array引用指向新数组
} finally {
lock.unlock(); // 释放锁
}
}
get()
读的时候不加锁,代码很简单。1
2
3
4
5
6
7public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
set()
1 | public E set(int index, E element) { |
remove()
同样地,CopyOnWriteArrayList有两个remove(),先来看一下根据下标删除元素的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public 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];
// 拷贝旧数组0到index处的到新数组0到index
System.arraycopy(elements, 0, newElements, 0, index);
// 拷贝旧数组index+1到最后处的到新数组index到最后
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements); // 将array引用指向新数组
}
return oldValue;
} finally {
lock.unlock(); // 释放锁
}
}
接着看一下删除第一个相同元素的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public boolean remove(Object o) {
final ReentrantLock lock = this.lock;
lock.lock(); // 获取锁
try {
Object[] elements = getArray();
int len = elements.length;
if (len != 0) {
int newlen = len - 1;
Object[] newElements = new Object[newlen];
// 删除第一个出现的相同元素
for (int i = 0; i < newlen; ++i) {
if (eq(o, elements[i])) {
// 将相同元素后面的元素复制到新数组末尾
for (int k = i + 1; k < len; ++k)
newElements[k-1] = elements[k];
setArray(newElements); // 将array引用指向新数组
return true;
} else
newElements[i] = elements[i];
}
if (eq(o, elements[newlen])) {
setArray(newElements);
return true;
}
}
return false;
} finally {
lock.unlock(); // 释放锁
}
}