CopyOnWriteArrayList源码分析

CopyOnWriteArrayList源码简析

在开发中,往往读操作的使用频率会远高于其他操作,而CopyOnWriteArrayList就是一种读操作效率远高于写操作效率的List,一起来看看吧

CopyOnWriteArrayList类图:

image.png

CopyOnWrite思想

CopyOnWrite简称COW,从命名上能出是写入时复制。意思是大家公共访问一个资源,如果有人想要修改这个资源的时候,就需要复制一个副本,去修改这个副本,而对于其他人来说访问的资源还是原来的,不会发生变化。

初始化CopyOnWriteArrayList

CopyOnWriteArrayList底层也是数组实现,和集合一样拥有添加、删除、修改和读取操作。其中添加、删除、修改操作时都需要进行加锁,读操作不会加锁。我们这里只分析添加和读取操作区别,删除修改和添加的原理基本没什么区别。
初始化方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
/**
* Creates an empty list.
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}

初始化只会创建一个空的数组,并将array指向它。

添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
//array指向新数组
setArray(newElements);
return true;
} finally {
//释放锁
lock.unlock();
}
}

添加数组的步骤如下:

  1. 获得独占锁,将添加功能加锁
  2. 获取原来的数组,并得到其长度
  3. 创建一个长度为原来数组长度+1的数组,并拷贝原来的元素给新数组
  4. 追加元素到新数组末尾
  5. 指向新数组
  6. 释放锁

添加这一过程是线程安全的,CopyOnWrite的核心思想就是每次添加修改的时候拷贝一个新的资源去修改,add方法在拷贝新资源的时候会讲数组的容量+1,这样虽然每次添加元素都会浪费一定的空间,单数素组的长度刚好和元素一样的时候,也就一定程度避免了扩容的开销。

获取元素

1
2
3
4
5
6
7
8
9
10
11
12
final Object[] getArray() {
return array;
}

private E get(Object[] a, int index) {
return (E) a[index];
}


public E get(int index) {
return get(getArray(), index);
}

因读操作天然就是安全的,因此获取元素的方法很简单。只需要根据索引获取相应的元素即可。

总结

CopyOnWriteArrayList都是线程安全的List,底层是数组实现的,而CopyOnWriteArrayList的读操作是不加锁的,而且不需要扩容,通过COW思想就能使数组容量满足要求。实现了RandomAccess接口,支持随机读取,因此更加推荐使用for循环进行遍历。在开发中,读操作会远远多于其他操作,因此使用CopyOnWriteArrayList集合效率更高。