一、CopyOnWriteArrayList源码
1.1 成员变量
//可重入锁
final transient ReentrantLock lock = new ReentrantLock();
//对象数组,注意用到volatile修饰
private transient volatile Object[] array;
1.2 构造方法
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* 使用指定 Collection 来构造 CopyOnWriteArrayList
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
if (c.getClass() != ArrayList.class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
/**
* 复制一份给定的数组
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
1.3 add操作
CopyOnWriteArrayList的add
//CopyOnWriteArrayList的add
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();
}
}
加锁,直接复制原有的数组,新元素加入新数组,将原来的数组指针指向新的数组。
ArrayList的add
//ArrayList的add
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
1.4 get操作
CopyOnWriteArrayList的get
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
private E get(Object[] a, int index) {
return (E) a[index];
}
没有加锁。
ArrayList的get
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
1.5 remove操作
CopyOnWriteArrayList的remove
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
加锁,对除了要删除的元素进行复制,将原数组的指针指向新的数组。
ArrayList的remove
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
1.6 小结
- CopyOnWriteArrayList的读方法和ArrayList的读方法没有区别,都没加锁。
- CopyOnWriteArrayList的写方法都加了可重入锁,都是对原数组进行复制并做相应的修改,然后将数组指针指向新的数组。
- 写写阻塞,读写不阻塞。
二、CopyOnWriteArrayList常见面试问题
2.1 CopyOnWriteArrayList和ArrayList的区别
CopyOnWriteArrayList是线程安全的,通过加可重入锁实现,比如两个线程同时add操作不会出现一个线程覆盖掉另一个线程add的值;ArrayList线程不安全。
2.2 CopyOnWriteArrayList的优缺点
- 线程安全。
- 写操作时完全拷贝数组,消耗内存。
- 如果写操作还没完成,那么多操作会读到旧值。读操作有延迟。
三、总结
虽然CopyOnWriteArrayList是线程安全的,但是存在读滞后于写的情况,所以适用于读多写少的并发场景。写操作需要复制数组,消耗内存严重。