当前位置: 首页 > 后端技术 > Java

JavaCopyOnWriteArrayList详解

时间:2023-04-01 14:16:53 Java

介绍CopyOnWriteArrayList是ArrayList的线程安全版本,内部也是通过数组实现的。每次修改数组时,都会完整复制一个新数组进行修改。修改后替换旧数组,保证只阻塞写操作,不阻塞读操作,实现读写分离。继承系统公共类CopyOnWriteArrayList实现了List、RandomAccess、Cloneable、java.io.Serializable{...}CopyOnWriteArrayList实现了List、RandomAccess、Cloneable、java.io.Serializable等接口。CopyOnWriteArrayList实现了List,提供了增删改查等基本操作。CopyOnWriteArrayList实现了RandomAccess,它提供了随机访问能力。CopyOnWriteArrayList实现了Cloneable并且可以被克隆。CopyOnWriteArrayList实现了Serializable并且可以被序列化。源码解析属性/**修改时用来加锁*/finaltransientReentrantLocklock=newReentrantLock();/**真正存放元素的地方只能通过getArray()/setArray()访问*/privatetransientvolatileObject[]array;lock:修改时加锁,使用transient修饰表示不会自动序列化。array:存放元素的地方,使用transient修饰表示不自动序列化,使用volatile修饰表示一个线程对该字段的修改立即对另一个线程可见。构造函数创建一个空数组。publicCopyOnWriteArrayList(){//所有对数组的操作都是通过setArray()和getArray()setArray(newObject[0]);}finalvoidsetArray(Object[]a){array=a;}publicCopyOnWriteArrayList(Collectionc){对象[]元素;if(c.getClass()==CopyOnWriteArrayList.class)//如果c也是CopyOnWriteArrayList类型//那么直接取其数组并使用elements=((CopyOnWriteArrayList)c).getArray();else{//否则调用其toArray()方法将集合元素转换为数组elements=c.toArray();//这里c.toArray()不一定返回Object[]类型//详见ArrayList中的分析if(elements.getClass()!=Object[].class)elements=Arrays.copyOf(elements,elements.长度,对象[].class);}setArray(elements);}如果c是CopyOnWriteArrayList类型,直接将其数组赋给当前list数组。请注意,这是一个浅拷贝,两个集合共享同一个数组。如果c不是CopyOnWriteArrayList类型,则将c的所有元素复制到当前列表的数组中。publicCopyOnWriteArrayList(E[]toCopyIn){setArray(Arrays.copyOf(toCopyIn,toCopyIn.length,Object[].class));}将toCopyIn的元素复制到当前列表的数组中。add(Ee)方法在末尾添加一个元素。publicbooleanadd(Ee){finalReentrantLocklock=this.lock;//锁定lock.lock();try{//获取旧数组Object[]elements=getArray();intlen=elements.length;//将旧数组的元素复制到新数组//新数组的大小是旧数组的大小加1Object[]newElements=Arrays.copyOf(elements,len+1);//将元素放在末尾newElements[len]=e;设置数组(新元素);返回真;}finally{//释放锁lock.unlock();}}锁;获取元素数组;创建一个新数组,其大小为原数组长度加1,并将原数组的元素Copy到新数组中;将新添加的元素放在新数组的末尾;将新数组赋值给当前对象的数组属性,覆盖原数组;开锁。add(intindex,Eelement)方法在指定索引处添加一个元素。publicvoidadd(intindex,Eelement){finalReentrantLocklock=this.lock;//锁定lock.lock();try{//获取旧数组Object[]elements=getArray();intlen=元素。长度;//检查是否越界,可以等于lenif(index>len||index<0)thrownewIndexOutOfBoundsException("Index:"+index+",Size:"+len);对象[]新元素;intnumMoved=len-索引;if(numMoved==0)//如果插入位置是最后一位//则复制一个n+1数组,前n个元素与旧数组一致newElements=Arrays.copyOf(elements,len+1);else{//如果插入位置不是最后一个//则创建一个新的n+1数组newElements=newObject[len+1];//将旧数组的前一个索引的元素复制到新数组System.数组复制(元素,0,新元素,0,索引);//将索引和后面的元素向后移动一位,复制到新数组中//这恰好是索引位置为空System.arraycopy(elements,索引,新元素,索引+1,numMoved);}//将元素放在索引处newElements[index]=element;设置数组(新元素);}finally{//释放锁lock.unlock();}}锁;检查索引是否合法。如果不合法,则抛出IndexOutOfBoundsException。请注意,索引等于len也是合法的;如果索引等于数组长度(即数组最后一位加1),则复制一个len+1的数组;if如果索引不等于数组长度,则新建一个len+1的数组,按照索引位置分成两部分。将索引前(不包含)的部分复制到新数组索引前(不包含)的部分,将索引后(包含)的位置复制到新数组后(不包含)的位置新数组的索引,索引位置留空;将索引位置指定为要添加的元素;将新数组赋值给当前对象的数组属性,并覆盖原数组;开锁;addIfAbsent(Ee)方法添加一个元素,如果该元素不存在于集合中publicbooleanaddIfAbsent(Ee){//获取一个元素数组,命名为快照Object[]snapshot=getArray();//判断元素不存在,直接返回false//如果存在,调用addIfAbsent()方法添加元素returnindexOf(e,snapshot,0,snapshot.length)>=0?false:addIfAbsent(e,snapshot);}privatebooleanaddIfAbsent(Ee,Object[]snapshot){finalReentrantLocklock=this.lock;}//锁定lock.lock();try{//重新获取旧数组Object[]current=getArray();intlen=current.length;//如果快照与刚刚获取的数组不一致//表示有修改if(snapshot!=current){//重新检查元素是否在刚刚获取的数组中intcommon=Math.min(snapshot.length,len);for(inti=0;i=0)返回false;}//复制一个n+1数组Object[]newElements=Arrays.copyOf(current,len+1);//将元素放在末尾newElements[len]=e;设置数组(新元素);返回真;}finally{//释放锁lock.unlock();}}检查数组快照中是否存在该元素;存在则直接返回false,不存在则调用addIfAbsent(Ee,Object[]snapshot)处理;锁;如果当前数组不等于传入的快照,说明有修改,检查当前数组中是否存在要添加的元素,存在则直接返回false;复制一个新数组,长度等于原数组长度加1,将原数组的元素复制到新数组中;将新元素添加到数组末尾一位;将新数组赋值给当前对象的数组属性,并覆盖原数组;开锁;get(intindex)获取指定索引处的元素,支持随机访问,时间复杂度为O(1)publicEget(intindex){//获取元素不需要加锁//直接返回索引位置的元素//这里没有做越界检查,因为数组本身会做越界检查returnget(getArray(),index);}finalObject[]getArray(){returnarray;}privateEget(Object[]a,intindex){return(E)a[index];}获取元素数组;返回数组指定索引位置的元素;remove(intindex)方法删除索引位置的指定元素。publicEremove(intindex){finalReentrantLocklock=this.lock;//锁定lock.lock();try{//获取旧数组Object[]elements=getArray();intlen=elements.length;EoldValue=get(元素,索引);intnumMoved=len-索引-1;if(numMoved==0)//如果去掉最后一位//则直接复制一个n-1的新数组,最后自动删除一位setArray(Arrays.copyOf(elements,len-1));else{//如果最后一位没有被移除//那么创建一个新的n-1数组Object[]newElements=newObject[len-1];//将之前索引的元素复制到新数组中System.arraycopy(elements,0,newElements,0,index);//将索引后面的元素(不包括)向前移动一位//这只是覆盖索引位置,相当于删除System.arraycopy(elements,index+1,newElements,index,numMoved);设置数组(新元素);}返回旧值;}finally{//释放锁lock.unlock();}}锁;获取指定索引位置的元素元素的旧值;如果移除了最后一个元素,则将原数组的前len-1个元素复制到新数组中,并将新数组赋值给当前对象的array属性;如果被移除的元素不是最后一个元素,则创建一个长度为len-1的新数组,并将原数组中除指定索引位置外的所有元素复制到新数组中,并将新数组赋值给数组当前对象的属性;解锁并返回旧值;size()方法返回数组的长度publicintsize(){//获取元素个数不需要加锁//直接返回数组的长度returngetArray().length;}为什么CopyOnWriteArrayList没有大小属性???因为每次修改都是复制一个可以存放目标元素个数的数组,所以不需要size属性。数组的长度就是集合的大小,不像ArrayList数组的长度实际上大于集合的大小。例如,add(Ee)操作首先复制一个包含n+1个元素的数组,然后将新元素放在新数组的最后一个位置。此时新数组的长度为len+1,也就是setsizeup。综上所述,CopyOnWriteArrayList使用ReentrantLock加锁加锁,保证线程安全;CopyOnWriteArrayList的写操作必须先复制一个新数组,在新数组中进行修改,修改后用新数组替换旧数组,所以空间复杂度为O(n),性能比较低;CopyOnWriteArrayList的读操作支持随机访问,时间复杂度为O(1);CopyOnWriteArrayList采用读写分离的思想,读操作不加锁,写操作加锁,写操作占用内存空间大,适合读多写少的场合;CopyOnWriteArrayList只保证最终一致性,不保证实时一致性。