本系列文章所描述的所有类或接口都是基于 JDK 1.7的源码,在其它 JDK 的实现方式中可能会有所不同。
一、简单介绍
CopyOnWriteArrayList 是一个线程安全、并且在读操作时无锁的 ArrayList,其具体实现方法如下。
二、CopyOnWriteArrayList()
和 ArrayList 不同,此步的做法为创建一个大小为 0 的数组。
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
final void setArray(Object[] a) {
array = a;
}
三、add(E)
add 方法并没有加上 synchronized 关键字,它通过使用 ReentrantLock 来保证线程安全。
除了使用 ReentrantLock 来保证线程安全外,此处和 ArrayList 的不同是每次都会创建一个新的 Object 数组,此数组的大小为当前数组大小加 1,将之前数组中的内容复制到新的数组中,并将新增加的对象放入数组末尾,最后做引用切换将新创建的数组对象赋值给全局的数组对象。
四、remove(E)
和 add 方法一样,此方法也通过 ReentrantLock 来保证其线程安全,但它和 ArrayList 删除元素采用的方式并不一样。
首先创建一个比当前数组小 1 的数组,遍历新数组,如找到 equals 或均为 null 的元素,则将之后的元素全部赋值给新的数组对象,并做引用切换,返回 true;如未找到,则将当前的元素赋值给新的数组对象,最后特殊处理数组中的最后一个元素,如最后一个元素等于要删除的元素,则将当前数组对象赋值为新创建的数组对象,完成删除操作,如最后一个元素也不等于要删除的元素,那么返回 false。
此方法和 ArrayList 除了锁不同外,最大的不同在于其复制过程并没有调用 System 的 arrayCopy 来完成,理论上来说会导致性能有一定下降。
五、get(int)
此方法非常简单,直接获取当前数组对应位置的元素,这种方法是没有加锁保护的,因此可能会出现读到脏数据的现象。但相对而言,性能会非常高,对于写少读多且脏数据影响不大的场景而言,CopyOnWriteArrayList 是不错的选择。
六、iterator()
调用 iterator 方法后创建一个新的 COWIterator 对象实例,并保存了一个当前数组的快照,在调用 next 遍历时则仅对此快照数组进行遍历,因此遍历 CopyOnWriteArrayList 时不会抛出 ConcurrentModificatiedException。
从以上的分析可见,CopyOnWriteArrayList 基于 ReentrantLock 保证了增加元素和删除元素动作的互斥。在读上没有做任何锁操作,这样就保证了读的性能,带来的副作用是有些时候可能会读取到脏数据。