CopyOnWriteArrayList:是一个并发的线程安全的集合,内部存储结构采用Object[ ]数组,使用ReentrantLock来保证线程安全,允许多线程并发读取,但只允许单线程写入
Copy-On-Write简称cow,是一种利于集合并发访问的优化策略。基本思想是:在集合增删改元素时,并不会直接写入,而是先复制,将元素复制到新容器,再写入,完成之后再将原容器的引用指向新容器。
下面,我们通过给CopyOnWriteArrayList的部分源码加注释来进一步认识这个并发安全的集合 :
首先是CopyOnWriteArrayList的构造方法:
//无参构造方法:初始化数组
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
//有参构造方法
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
//判断传入的集合是否是CopyOnWriteArrayList类
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) //判断数组是否为Object数组
//不是Object数组,按照Object数组存储
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
根据指定下标位置修改值 set(下标,修改值)
//修改、更新
public E set(int index, E element) {
//创建ReentrantLock锁对象
final ReentrantLock lock = this.lock;
lock.lock();//加锁
try {
Object[] elements = getArray(); //获取数组
E oldValue = get(elements, index);//获取传入的下标对应修改前的值
//判断修改前的值和修改后的值是否相等
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
setArray(elements);
}
return oldValue;//返回被修改前的值
} finally {
lock.unlock();//释放锁
}
}
添加新元素值 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;
setArray(newElements);//存回新数组
return true;//返回成功
} finally {
lock.unlock();//释放锁
}
}
在指定位置添加新元素 add(下标,添加元素的对象)
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) //判断传入的参数下标位置是否大于数组长度或者小于0
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len); //传入参数的下标不规范,抛异常
Object[] newElements; //创建新数组
int numMoved = len - index; //数组长度-传入的下标
if (numMoved == 0)
//数组长度-下标=0表示添加元素的位置在数组末尾处
newElements = Arrays.copyOf(elements, len + 1); //创建新数组,复制原数组,长度+1
else { //如果添加新元素不在末尾位置
newElements = new Object[len + 1]; //初始化新数组,长度+1
System.arraycopy(elements, 0, newElements, 0, index); //复制指定添加位置之前的index个元素到新数组
System.arraycopy(elements, index, newElements, index + 1,
numMoved); //从指定添加位置的后一位复制到末尾处至新数组
}
newElements[index] = element; //将需添加元素按照指定位置添加至新数组
setArray(newElements); //将新数组存回去
} finally {
lock.unlock();
}
}
按照指定下标删除元素 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; //数组长度-删除位置的下表-1
if (numMoved == 0) //数组长-删除位置下标-1==0 表示删除的是末尾元素
//删除的是末尾元素,直接重新存入数组长度-1个值到新数组,末尾元素没复制上,就达到删除目的
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);//跳过被删除位置,从该位置的后一位开始复制,
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) //如果删除区间右边=0,表示删除的区间是数组末尾部分
setArray(Arrays.copyOf(elements, newlen)); //删除的是数组末尾部分,直接从数组起始部分存入剩余元素个数的值
else {
//删除区间不在末尾部分
Object[] newElements = new Object[newlen]; //创建新数组,长度为剩余元素个数
System.arraycopy(elements, 0, newElements, 0, fromIndex); //从下表为0复制到删除区间的起始位置
System.arraycopy(elements, toIndex, newElements,
fromIndex, numMoved); //从删除区间的结束位置复制到原数组的末尾处,复制删除区间以右的元素个数
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;
//如果数组长度不等于0
if (len != 0) {
// temp array holds those elements we know we want to keep
int newlen = 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) { //如果新数组存入元素的个数不等于数组长度
setArray(Arrays.copyOf(temp, newlen)); //则将型数组中有元素的返回
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
清空集合
//清空集合
public void clear() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
setArray(new Object[0]); //存入一个长度为0的新数组
} finally {
lock.unlock();
}
}
在原集合指定位置添加集合
//在指定位置添加集合
public boolean addAll(int index, Collection<? extends E> c) {
Object[] cs = c.toArray();//将传入的集合转成数组
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();//获取原数组
int len = elements.length; //原数组的长度
if (index > len || index < 0) //判断传入的下标值是否小于0或大于数组长度
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len); //不符合数组下标的规范 ,抛异常
if (cs.length == 0)//判断插入值的数组长度是否等于0
return false; //返回false
int numMoved = len - index; //原数组长度-传入的指定下标位置
Object[] newElements; //创建新数组的引用
if (numMoved == 0) //判断数组插入的位置是否为原数组末尾
//将原数组复制至新数组,新数组长度为原数组长度+插入数组长度
newElements = Arrays.copyOf(elements, len + cs.length);
else { //数组插入位置不在原数组末尾处
newElements = new Object[len + cs.length]; //初始化新数组,长度为原数组长+插入数组长度之和
System.arraycopy(elements, 0, newElements, 0, index); //将原数组从起始位置至插入位置复制到新数组
System.arraycopy(elements, index,
newElements, index + cs.length,
numMoved); //将原数组从插入位置之后的元素复制到新数组的(插入位置+插入数组长度)的位置
}//判断结束,无论插入位置在哪,都有如下共同操作步骤
System.arraycopy(cs, 0, newElements, index, cs.length); //将传入的集合转化的数组全部元素传入新数组的index(传入下标)的位置
setArray(newElements);//存储新数组
return true;
} finally {
lock.unlock(); //释放锁
}
}
}