CopyOnWriteArrayList 源码分析

摘要:

  1. CopyOnWriteArrayList 是怎么保证线程安全的
  2. 适用的场景

TOP 带着问题看源码

  1. CopyOnWriteArrayList 是怎么保证线程安全的
  2. 适用的场景

1. 基本介绍

顾名思义,这是一个每次写入都采用先复制再写入的方式来实现的线程安全的 List。这样的好处是可以读写并行,而且实现简单。

2. 成员变量分析

1
2
3
4
5
6
7
8
9
10
11
12
13
// lock 锁
final transient ReentrantLock lock = new ReentrantLock();
// 存放数据的数组
private transient volatile Object[] array;

// getter
final Object[] getArray() {
return array;
}
// setter
final void setArray(Object[] a) {
array = a;
}

3. 核心方法分析

3.1 add(E e)

核心逻辑就是使用数组copy把旧的数据复制到新的数组(比旧的数组长度大1),然后把存放数据的数组指向新的数组。加锁是解决线程不安全问题,可以明白问题 TOP 1 的线程安全问题是由 COW+Lock 来保证的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}
}

3.2 get(int index)

获取指定 index 的数据比较简单,可以看到查询是无锁的

1
2
3
4
5
6
7
public E get(int index) {
return get(getArray(), index);
}

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

4. 总结

经过前面的分析,可以发现其更新操作都是贯彻 COW 思想,那就有可能写入还在复制副本期间读的是旧的数据,回到问题 TOP 2 那对应的场景也可以猜到:

适用于写少读多且能容忍读写的短暂不一致