首页>>后端>>java->CopyOnWriteArrayList 源码解析

CopyOnWriteArrayList 源码解析

时间:2023-11-30 本站 点击:1

CopyOnWriteArrayList为线程安全的ArrayList,这节分析下CopyOnWriteArrayList的源码,基于JDK1.8。

类结构

CopyOnWriteArrayList类关系图:

CopyOnWriteArrayList实现了List接口的所有方法,主要包含如下两个成员变量:

//可重入锁,用于对写操作加锁finaltransientReentrantLocklock=newReentrantLock();//Object类型数组,存放数据,volatile修饰,目的是一个线程对这个字段的修改另外一个线程立即可见privatetransientvolatileObject[]array;

CopyOnWriteArrayList中并没有和容量有关的属性或者常量,下面通过对一些常用方法的源码解析,就可以知道原因。

方法解析

构造函数

CopyOnWriteArrayList()空参构造函数:

publicCopyOnWriteArrayList(){setArray(newObject[0]);}finalvoidsetArray(Object[]a){array=a;}

无参构造函数直接创建了一个长度为0的Object数组。

CopyOnWriteArrayList(Collection<? extends E> c)

publicCopyOnWriteArrayList(Collection<?extendsE>c){Object[]elements;if(c.getClass()==CopyOnWriteArrayList.class)//如果集合类型就是CopyOnWriteArrayList,则直接将其array赋值给当前CopyOnWriteArrayListelements=((CopyOnWriteArrayList<?>)c).getArray();else{//如果不是CopyOnWriteArrayList类型,则将集合转换为数组elements=c.toArray();//就如ArrayList源码分析所述那样,c.toArray()返回类型不一定是Object[].class,所以需要转换if(elements.getClass()!=Object[].class)elements=Arrays.copyOf(elements,elements.length,Object[].class);}//设置array值setArray(elements);}

CopyOnWriteArrayList(E[] toCopyIn)

publicCopyOnWriteArrayList(E[]toCopyIn){//入参为数组,拷贝一份赋值给arraysetArray(Arrays.copyOf(toCopyIn,toCopyIn.length,Object[].class));}

add(E e)

add(E e)往CopyOnWriteArrayList末尾添加元素:

publicbooleanadd(Ee){//获取可重入锁finalReentrantLocklock=this.lock;//上锁,同一时间内只能有一个线程进入lock.lock();try{//获取当前array属性值Object[]elements=getArray();//获取当前array数组长度intlen=elements.length;//复制一份新数组,新数组长度为当前array数组长度+1Object[]newElements=Arrays.copyOf(elements,len+1);//在新数组末尾添加元素newElements[len]=e;//新数组赋值给array属性setArray(newElements);returntrue;}finally{//锁释放lock.unlock();}}finalObject[]getArray(){returnarray;}

可以看到,add操作通过ReentrantLock来确保线程安全。通过add方法,我们也可以看出CopyOnWriteArrayList修改操作的基本思想为:复制一份新的数组,新数组长度刚好能够容纳下需要添加的元素;在新数组里进行操作;最后将新数组赋值给array属性,替换旧数组。这种思想也称为“写时复制”,所以称为CopyOnWriteArrayList。

此外,我们可以看到CopyOnWriteArrayList中并没有类似于ArrayList的grow方法扩容的操作。

add(int index, E element)

add(int index, E element)指定下标添加指定元素:

publicvoidadd(intindex,Eelement){//获取可重入锁finalReentrantLocklock=this.lock;//上锁,同一时间内只能有一个线程进入lock.lock();try{//获取当前array属性值Object[]elements=getArray();//获取当前array数组长度intlen=elements.length;//下标检查if(index>len||index<0)thrownewIndexOutOfBoundsException("Index:"+index+",Size:"+len);Object[]newElements;intnumMoved=len-index;if(numMoved==0)//numMoved为0,说明是在末尾添加,过程和add(Ee)方法一致newElements=Arrays.copyOf(elements,len+1);else{//否则创建一个新数组,数组长度为旧数组长度值+1newElements=newObject[len+1];//分两次复制,分别将index之前和index+1之后的元素复制到新数组中System.arraycopy(elements,0,newElements,0,index);System.arraycopy(elements,index,newElements,index+1,numMoved);}//在新数组的index位置添加指定元素newElements[index]=element;//新数组赋值给array属性,替换旧数组setArray(newElements);}finally{//锁释放lock.unlock();}}

set(int index, E element)

set(int index, E element)设置指定位置的值:

publicEset(intindex,Eelement){//获取可重入锁finalReentrantLocklock=this.lock;//上锁,同一时间内只能有一个线程进入lock.lock();try{//获取当前array属性值Object[]elements=getArray();//获取当前array指定index下标值EoldValue=get(elements,index);if(oldValue!=element){//如果新值和旧值不相等intlen=elements.length;//复制一份新数组,长度和旧数组一致Object[]newElements=Arrays.copyOf(elements,len);//修改新数组index下标值newElements[index]=element;//新数组赋值给array属性,替换旧数组setArray(newElements);}else{//即使新值和旧值一致,为了确保volatile语义,需要重新设置arraysetArray(elements);}returnoldValue;}finally{//释放锁lock.unlock();}}privateEget(Object[]a,intindex){return(E)a[index];}

remove(int index)

remove(int index)删除指定下标元素:

publicEremove(intindex){//获取可重入锁finalReentrantLocklock=this.lock;//上锁,同一时间内只能有一个线程进入try{//获取当前array属性值Object[]elements=getArray();//获取当前array长度intlen=elements.length;//获取旧值EoldValue=get(elements,index);intnumMoved=len-index-1;if(numMoved==0)//如果删除的是最后一个元素,则将当前array设置为新数组//新数组长度为旧数组长度-1,这样刚好截去了最后一个元素setArray(Arrays.copyOf(elements,len-1));else{//分段复制,将index前的元素和index+1后的元素复制到新数组//新数组长度为旧数组长度-1Object[]newElements=newObject[len-1];System.arraycopy(elements,0,newElements,0,index);System.arraycopy(elements,index+1,newElements,index,numMoved);//设置arraysetArray(newElements);}returnoldValue;}finally{//锁释放lock.unlock();}}

可以看到,CopyOnWriteArrayList中的增删改操作都是在新数组中进行的,并且通过加锁的方式确保同一时刻只能有一个线程进行操作,操作完后赋值给array属性,替换旧数组,旧数组失去了引用,最终由GC回收。

get(int index)

publicEget(intindex){returnget(getArray(),index);}finalObject[]getArray(){returnarray;}

可以看到,get(int index)操作是分两步进行的:

通过getArray()获取array属性值;

获取array数组index下标值。

这个过程并没有加锁,所以在并发环境下可能出现如下情况:

线程1调用get(int index)方法获取值,内部通过getArray()方法获取到了array属性值;

线程2调用CopyOnWriteArrayList的增删改方法,内部通过setArray方法修改了array属性的值;

线程1还是从旧的array数组中取值。

所以get方法是弱一致性的。

size()

publicintsize(){returngetArray().length;}

size()方法返回当前array属性长度,因为CopyOnWriteArrayList中的array数组每次复制都刚好能够容纳下所有元素,并不像ArrayList那样会预留一定的空间。所以CopyOnWriteArrayList中并没有size属性,元素的个数和数组的长度是相等的。

迭代器

publicCopyOnWriteArrayList(){setArray(newObject[0]);}finalvoidsetArray(Object[]a){array=a;}0

可以看到,迭代器也是弱一致性的,并没有在锁中进行。如果其他线程没有对CopyOnWriteArrayList进行增删改的操作,那么snapshot还是创建迭代器时获取的array,但是如果其他线程对CopyOnWriteArrayList进行了增删改的操作,旧的数组会被新的数组给替换掉,但是snapshot还是原来旧的数组的引用:

publicCopyOnWriteArrayList(){setArray(newObject[0]);}finalvoidsetArray(Object[]a){array=a;}1

输出结果仅为hello。

总结

CopyOnWriteArrayList体现了写时复制的思想,增删改操作都是在复制的新数组中进行的;

CopyOnWriteArrayList的取值方法是弱一致性的,无法确保实时取到最新的数据;

CopyOnWriteArrayList的增删改方法通过可重入锁确保线程安全;

CopyOnWriteArrayList线程安全体现在多线程增删改不会抛出java.util.ConcurrentModificationException异常,并不能确保数据的强一致性;

同一时刻只能有一个线程对CopyOnWriteArrayList进行增删改操作,而读操作没有限制,并且 CopyOnWriteArrayList增删改操作都需要复制一份新数组,增加了内存消耗,所以CopyOnWriteArrayList适合读多写少的情况。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/java/4972.html