1. CopyOnWriteArrayList如何实现线程安全?
CopyOnWriteArrayList同ArrayList一样实现了LIst接口,不同的是CopyOnWriteArrayList是线程安全的,根据字面意思是在写的时候复制,再结合源码,我们明显能看到,CopyOnWriteArrayList的一些涉及到修改集合元素的方法如add(),remove()...时使用ReentrantLock
加锁,然后复制一个数组"副本"出来,修改操作都是在副本数组里完成的,操作完成后再替换原来的数组为"副本"数组。
这样做的好处是多个线程想要修改或者删写的时候只能有一个线程得到锁来执行操作,直到操作完成后释放锁其他线程才能继续抢占,而对于只读操作的线程来说,get()方法没有加锁,允许多个线程同时读取,而且读取到的一直都是旧数据。
相对于Vector集合的每个方法都进行了加锁,CopyOnWriteArrayList的读操作不加锁,而且在开发中,读操作一般会多于其他操作,所以多线程场景下使用CopyOnWriteArrayList集合效率更高。
2. CopyOnWriteArrayList部分源码解析:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array; //集合内部存储
final Object[] getArray() {
return array; //返回当前数组
}
final void setArray(Object[] a) {
array = a;
}
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
public E set(int index, E element) {
final ReentrantLock lock = this.lock; //获得锁
lock.lock(); //加锁
try {
Object[] elements = getArray(); //获得当前数组
E oldValue = get(elements, index); //根据下标拿到旧元素
if (oldValue != element) { //如果新元素与旧元素不同
int len = elements.length; //获取elements数组长度
Object[] newElements = Arrays.copyOf(elements, len); //复制
newElements[index] = element; //替换
setArray(newElements); //替换数组,将array里的元素替换成newElements里的元素
} else {
//为了确保volatile语义,执行替换操作
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue; //返回被替换的元素
} finally {
lock.unlock(); //释放锁
}
}
public boolean add(E e) {
final ReentrantLock lock = this.lock; //获得锁
lock.lock(); //加锁
try {
Object[] elements = getArray(); //获取当前数组
int len = elements.length; //获取elements数组长度
Object[] newElements = Arrays.copyOf(elements, len + 1); //复制elements内容给newElements
newElements[len] = e; //将元素e添加至newElements数组末尾
setArray(newElements); //替换数组,将array里的元素替换成newElements里的元素
return true;
} finally {
lock.unlock(); //释放锁
}
}
public void add(int index, E element) {
final ReentrantLock lock = this.lock; //获得锁
lock.lock(); //上锁
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0) //如果要添加元素至集合尾部
newElements = Arrays.copyOf(elements, len + 1); //复制elements数组中元素到newElements并令其长度加1
else {
newElements = new Object[len + 1]; //创建新的数组并且数组长度加1
System.arraycopy(elements, 0, newElements, 0, index); //将数组elements中的元素复制前index个到newElements数组中
System.arraycopy(elements, index, newElements, index + 1,
numMoved); //将elements数组中从下标为index的元素(包括)开始复制numMoved个元素到newElements数组的下标为index + 1的地方
}
newElements[index] = element; //将元素element赋值到数组newElements的下标为index的位置
setArray(newElements); //替换数组
} finally {
lock.unlock(); //释放锁
}
}
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]; //数组长度减1
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved); //按顺序复制除了要删除下标位置的元素以外的其他元素到新数组newElements
setArray(newElements); //替换数组
}
return oldValue; //返回要删除的元素
} finally {
lock.unlock(); //释放锁
}
}
void removeRange(int fromIndex, int toIndex) {
final ReentrantLock lock = this.lock; //获得锁
lock.lock(); //上锁
try {
Object[] elements = getArray(); //获得数组
int len = elements.length; //获得数组长度
if (fromIndex < 0 || toIndex > len || toIndex < fromIndex)
throw new IndexOutOfBoundsException(); //范围判断
int newlen = len - (toIndex - fromIndex); //获得删除后的数组长度
int numMoved = len - toIndex; //删除后的后段元素长度
if (numMoved == 0)
setArray(Arrays.copyOf(elements, newlen)); //如果要删除到末尾就就复制前newlen个元素并替换
else {
Object[] newElements = new Object[newlen];
System.arraycopy(elements, 0, newElements, 0, fromIndex);
System.arraycopy(elements, toIndex, newElements,
fromIndex, numMoved); //分段复制elements数组元素给数组newElements
setArray(newElements); //替换数组
}
} finally {
lock.unlock(); //释放锁
}
}
public boolean retainAll(Collection<?> c) { //得到调用此方法的集合和参数集合的交集并赋给调用者
if (c == null) throw new NullPointerException();
final ReentrantLock lock = this.lock; //获得锁
lock.lock(); //加锁
try {
Object[] elements = getArray(); //获得数组
int len = elements.length; //获得数组长度
if (len != 0) {
// temp array holds those elements we know we want to keep
int newlen = 0; //交集数组的长度,先初始化为0
Object[] temp = new Object[len]; //创建一个等长的临时数组
for (int i = 0; i < len; ++i) {
Object element = elements[i];
if (c.contains(element))
temp[newlen++] = element; //便利拿到两个数组中都有的元素
}
if (newlen != len) { //如果这里相等就说明调用此方法的集合的元素都包含于参数集合,此时会返回false
setArray(Arrays.copyOf(temp, newlen)); //替换数组
return true;
}
}
return false; //调用此方法的集合内的数组长度为0返回false
} finally {
lock.unlock(); //释放锁
}
}
3.CopyOnWriteArrayList具有以下特性:
1 在保证并发读取的前提下,确保了写入时的线程安全;
2 由于每次写入操作时,进行了Copy复制原数组,所以无需扩容;
3 适合读多写少的应用场景。由于add()、set() 、 remove()等修改操作需要复制整个数组,所以会有内存开销大的问题。
4 CopyOnWriteArrayList由于只在写入时加锁,所以只能保证数据的最终一致性,不能保证数据的实时一致性。