TOP 带着问题看源码
- CopyOnWriteArrayList 是怎么保证线程安全的
- 适用的场景
1. 基本介绍
顾名思义,这是一个每次写入都采用先复制再写入的方式来实现的线程安全的 List。这样的好处是可以读写并行,而且实现简单。
2. 成员变量分析
1 2 3 4 5 6 7 8 9 10 11 12 13
| final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;
final Object[] getArray() { return array; }
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 那对应的场景也可以猜到:
适用于写少读多且能容忍读写的短暂不一致