前言
CopyOnWriteArrayList是一个线程安全的ArrayList,对ArrayList内部结构不太清楚的可以看看博主的这篇文章:从源码分析java容器之ArrayList,看完这篇文章,你会知道为什么ArrayList是非线程安全的。然后你再来看这篇文章,看看CopyOnWriteArrayList是如何保证线程安全的。
1、结构图
从结构图中,我们可以看出CopyOnWriteArrayList本质上还是一个List,内部数据结构还是数组。
2、分析源码
2.1、属性
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
//增加了一个ReentrantLock互斥锁
//我们应该能够猜到是保证增删的线程安全,保证了写的线程安全
final transient ReentrantLock lock = new ReentrantLock();
//内部数组增加了volatile修饰
//增加了数组多线程中在内存的可见性,保证了读的线程安全
private transient volatile Object[] array;
//以下省略……
}
2.2、构造方法
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
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);
}
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
2.3、add()方法
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;
//新数组指向Volatile修饰的array
setArray(newElements);
return true;
} finally {
//释放锁
lock.unlock();
}
}
上面的add()方法步骤是:
- 先copy旧数组到新数组
- 然后写入新增的元素
- 再将新数组指向Volatile修饰的array数组
有木有突然明白为什么它叫CopyOnWriteArrayList。
3、总结
- CopyOnWriteArrayList内部数据结构是数组。
- CopyOnWriteArrayList内部有一个ReentrantLock对象的成员变量lock,lock保证了写和删除操作的线程安全。
- CopyOnWriteArrayList内部的数组是用Volatile修饰,保证了读操作的线程安全。
- CopyOnWriteArrayList的扩容是每次+1,适合的场景是读多写少的场景。
结束语
通过本篇的分析,我们知道了CopyOnWriteArrayList相比ArrayList,如何保证了线程安全。
下一篇,我们将分析CopyOnWriteArraySet的实现。