CopyOnWriteArrayList
针对List、Map、Set、Deque等,java.util.concurrent包也提供了对应的并发集合类。例如CopyOnWriteArrayList。
Copy-On-Write简称COW,是一种用于集合的并发访问的优化策略。基本思想是:当我们往一个集合容器中写入元素的时候(添加、修改、删除)。并不会直接在集合容器中写入,而是先将当前集合容器进行Copy。复制出一个新的容器,然后在新的容器里写入元素,写入操作完成之后,在将原容器的引用指向新的容器。
这样做的好处是:实现对CopyOnWrite集合容器写入操作时的线程安全,但同时并不影响进行并发的读取操作。所以CopyOnWrite容器也是一种读写分离的思想。从JDK1.5开始Java并发包里面提供了两个使用CopyOnWrite机制实现的并发集合容器,它们是CopyOnWriteList和CopyOnWriteSet。
CopyOnWriteList相当于线程安全的ArrayList,内部存储结构采用Object[]数组,线程安全使用ReentrantLock实现,允许多个线程并发读取,但是只能有一个线程写入。
下面我们对部分源码进行分析:
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 写入操作时竞争的锁对象
final transient ReentrantLock lock = new ReentrantLock();
// 基于数组实现的,只能通过getArray()和setArray()来读写
private transient volatile Object[] array;
/**
* Gets the array. Non-private so as to also be accessible from
* CopyOnWriteArraySet class.
*/
final Object[] getArray() {
return array;
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
// 无参的构造方法,调用的时候内部创建一个大小为0的空数组
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
// 私有的get方法,用来返回当前数组a下标为index的元素
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
// 通过指定的下标获取指定的元素,调用get(Object[] a,int index)方法返回元素
public E get(int index) {
return get(getArray(), index);
}
// 用来给指定下标设置值,同时返回旧值,是一个写入操作,需要竞争到锁才能使用
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();// 加锁
try {
// 获取当前存储元素的数组
Object[] elements = getArray();
// 获取当前下标对应的旧元素
E oldValue = get(elements, index);
/*
* 判断:如果旧元素不等于新元素: 拷贝一个一样的数组,替换下标元素,然后写入array
*/
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
// 即时元素没有变化,也要写入array
setArray(elements);
}
// 返回旧的元素
return oldValue;
} finally {
// 释放锁
lock.unlock();
}
}
// 向集合中添加一个元素。同样是写入操作,需要竞争锁
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 先获取本来的数组array
Object[] elements = getArray();
int len = elements.length;
// 然后拷贝一个长度加一的数组,因为每次只能加入一个元素
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 将需要加入的元素放在新数组的末尾
newElements[len] = e;
// 写入array 覆盖原数组
setArray(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;
// 判断index是否合法
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + len);
// 新数组
Object[] newElements;
// 得到该下标距离末尾的长度
int numMoved = len - index;
if (numMoved == 0) // 如果等于0,意味着要添加在数组的末尾
// 将当前数组长度加一复制到一个新数组中,前面的元素与本来的数组相同
newElements = Arrays.copyOf(elements, len + 1);
else {
// 如果numMoved不为0,那么将新数组的长度设置为当前数组长度加一
newElements = new Object[len + 1];
// 拷贝旧数组从下标0到index-1的元素到新数组中
System.arraycopy(elements, 0, newElements, 0, index);
// 拷贝旧数组的其他元素到新数组下标为index+1到末尾,空出index的位置
System.arraycopy(elements, index, newElements, index + 1, numMoved);
}
// 将新元素加入新数组中
newElements[index] = element;
// 写入array,覆盖原数组
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;
// 通过get方法获取指定下标对应的旧元素
E oldValue = get(elements, index);
// 计算该下标距离数组末尾的长度
int numMoved = len - index - 1;
if (numMoved == 0)
// numMoved为0的时候,意味着需要删除的元素就是最后一个元素
// 将当前数组长度减一直接写入原数组array,覆盖
setArray(Arrays.copyOf(elements, len - 1));
else {
// numMoved不为0的时候,创建一个长度减一的新数组
Object[] newElements = new Object[len - 1];
// 拷贝原数组从下标0到index-1的元素到新数组
System.arraycopy(elements, 0, newElements, 0, index);
// 然后将原数组从index+1到结束复制到新数组的index下标位置,将原数组的index下标移除
System.arraycopy(elements, index + 1, newElements, index, numMoved);
// 然后覆盖原数组array
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)
// 如果numMoved为0,直接复制原数组从头复制到newlen位置
setArray(Arrays.copyOf(elements, newlen));
else {
// 如果numMoved不为0,创建一个新的数组,长度为newlen
Object[] newElements = new Object[newlen];
// 拷贝原数组从下标0到fromInde位置的元素到新数组
System.arraycopy(elements, 0, newElements, 0, fromIndex);
// 拷贝原数组从下标toIndex到末尾的元素到新数组下标fromIndex到末尾
System.arraycopy(elements, toIndex, newElements, fromIndex, numMoved);
// 覆盖原数组
setArray(newElements);
}
} finally {
lock.unlock();
}
}
// 删除指定集合中包含的所有元素
public boolean removeAll(Collection<?> c) {
// 判断集合是否为空
if (c == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 判断原数组的长度是否为0
if (len != 0) {
// temp array holds those elements we know we want to keep
// 不为0的时候,说明数组有值,数组无值直接返回 false
// 创建一个空数组temp,长度为len,用来暂时存放元素
int newlen = 0;// 表示新数组的索引位置
Object[] temp = new Object[len];
// 遍历原数组,获取每一个元素,将不包含在c中的元素放入新数组中
for (int i = 0; i < len; ++i) {
Object element = elements[i];
// 判断集合中是否存在该元素
if (!c.contains(element))
// 将c中不存在的元素加入空数组temp中
temp[newlen++] = element;
}
// 判断空函数的长度是否等于原数组
if (newlen != len) {
// 如果不等于,拷贝新数组,变相的删除了不包含在 c 中的元素
setArray(Arrays.copyOf(temp, newlen));
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
// 清空数组 需要竞争锁
public void clear() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 给原数组覆盖一个长度为0的数组,清空原数组的内容
setArray(new Object[0]);
} finally {
lock.unlock();
}
}
// 添加批量元素
public boolean addAll(Collection<? extends E> c) {
// 将需要加入的集合转换为数组
Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ? ((CopyOnWriteArrayList<?>) c).getArray()
: c.toArray();
// 如果长度为0,直接返回false
if (cs.length == 0)
return false;
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 如果原数组为空并且类型为Object,则直接将新数组覆盖给原数组
if (len == 0 && cs.getClass() == Object[].class)
setArray(cs);
else {
// 原数组不为空,则创建一个新数组,将原数组的内容拷贝给新数组,长度为原数组长度加需要添加的元素数组的长度
Object[] newElements = Arrays.copyOf(elements, len + cs.length);
// 将需要添加的元素拷贝给新数组
System.arraycopy(cs, 0, newElements, len, cs.length);
// 覆盖原数组
setArray(newElements);
}
return true;
} finally {
lock.unlock();
}
}
// 对集合内的元素进行排序,需要竞争锁 实现Comparator接口,定义排序规则
public void sort(Comparator<? super E> c) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原数组
Object[] elements = getArray();
// 拷贝原数组的内容到一个新数组
Object[] newElements = Arrays.copyOf(elements, elements.length);
@SuppressWarnings("unchecked")
// 进行排序操作
E[] es = (E[]) newElements;
Arrays.sort(es, c);
// 覆盖原数组
setArray(newElements);
} finally {
lock.unlock();
}
}
// 比较指定对象与此列表的相等性
public boolean equals(Object o) {
// 先比较内存地址 如果相等直接返回true
if (o == this)
return true;
// 判断该对象的类型是否属于List,否则直接返回false
if (!(o instanceof List))
return false;
// 将该对象向上转型为List
List<?> list = (List<?>) (o);
// 获取list的迭代器
Iterator<?> it = list.iterator();
// 获取原数组
Object[] elements = getArray();
int len = elements.length;
// 遍历原数组 判断元素是否相等,不相等或者该对象元素不够则直接返回false
for (int i = 0; i < len; ++i)
if (!it.hasNext() || !eq(elements[i], it.next()))
return false;
// 遍历结束后该对象仍然有元素 直接返回false
if (it.hasNext())
return false;
return true;
}
// 获取该集合的哈希值
public int hashCode() {
int hashCode = 1;
Object[] elements = getArray();
int len = elements.length;
for (int i = 0; i < len; ++i) {
Object obj = elements[i];
// 通过算法获取该集合的哈希值
hashCode = 31 * hashCode + (obj == null ? 0 : obj.hashCode());
}
return hashCode;
}
}
以上是对部分源代码的解析,因为源代码过多,所以只对关键部分进行解析
CopyOnWriteArrayList的特性:
1、在保证并发读取的前提下,确保了写入时的线程安全;
2、由于每次写入操作时,进行了Copy复制原数组,所以无序扩容;
3、适合读少写多的应用场景。由于add()、set()、remove()等修改操作需要复制整个数组,所以会有内存开销大的问题;
4、CopyOnWriteArrayList由于只在写入的适合加锁,所以只能保证数据的最终一致性,不能保证数据的实时一致性。