JAVA集合系列——CopyOnWriteArrayList实现原理
CopyOnWriteArrayList实现原理
特点
- COW机制实现
- 有序的
- 线程安全
- 读写分离
数据结构
COW就是Copy-On-Write,是程序设计的一种优化策略;当我们需要对一份共享的数据进行修改时,我们可以先将这份共享数据拷贝一份,待我们修改完成后再将这份拷贝放回原始引用处,所以COW实现机制的容器也被叫做即写时复制容器。这样做的好处就是在多线程的环境中,我们获取元素的时候仍然不需要加锁。CopyOnWriteArrayList底层和ArrayList一样,都是使用数组来保存所有元素的。1
2
3
4//锁,在新增修改是实现并发控制
transient final ReentrantLock lock = new ReentrantLock();
//数组,用来保存元素,volatile实现多线程时的可见性
private volatile transient Object[] array;
存取实现
初始化
CopyOnWriteArrayList和ArrayList的初始化有些不同,ArrayList有几个初始化方法,包括设置初始容量和加载因子等,但是CopyOnWriteArrayList没有初始容量和加载因子设置,默认构造函数初始化的容器容量为0。为什么CopyOnWriteArrayList没有初始容量和加载因子呢?大家考虑下。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//默认构造
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
//集合构造
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
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));
}
存储
新增元素的时候,仍然需要锁,不然并发环境下,每次新增的时候都拷贝一份副本,JVM受得了吗!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//新增
public boolean add(E e) {
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
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();
}
}
1 | //插入 |
1 | //替换 |
获取
CopyOnWriteArrayList的获取元素十分的简单,没有加锁,即使是在多线程并发的环境下。所以CopyOnWriteArrayList的获取效率十分的高,适用在读大于写的场景中。1
2
3
4//根据索引获取元素
public E get(int index) {
return (E)(getArray()[index]);
}
删除
1 | //根据索引删除元素 |
1 | //根据对象删除元素 |
1 | //删除索引范围内的元素 |
迭代
CopyOnWriteArrayList也有迭代器,但是是阉割版的,迭代器中不支持调用add,set,remove等方法,否则会抛出异常;当使用迭代器时,迭代器会使用一个引用指向当前的原始数据,所以并发环境下,其他线程修改元素数据的时候,不会影响到迭代的数据,是线程安全的。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 private static class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array **/
//用于保存迭代时的数据
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
...
public void remove() {
throw new UnsupportedOperationException();
}
public void set(E e) {
throw new UnsupportedOperationException();
}
public void add(E e) {
throw new UnsupportedOperationException();
}
}
相关
CopyOnWriteArrayList适用于读大于写的场景中,不适合大量的写操作,因为会产生内存大量占用的问题和数据一致性的问题;比如我们要添加多个元素到CopyOnWriteArrayList中的时候,避免使用循环add方法的方式,而是采用addAll的方式,这样就不会产生大量的内存占用,甚至内存溢出;在数据一致性要求比较严格的情景,也要避免使用CopyOnWriteArrayList,要知道CopyOnWriteArrayList只能保证数据的最终一致性,而不能保证实时的数据一致性。