简介
CopyOnWriteArrayList 是线程安全的 List。读写分离思想:读操作无锁;写操作(add/set/remove)加独占锁,复制一份新数组,修改新数组,最后将引用指向新数组。
旧数组不会被修改,所有读线程继续访问旧数组,不会阻塞读。适合读多写少场景(缓存、事件监听);写操作会复制数组,大数据量写开销大。
一、锁与底层数组定义
// 独占锁,所有修改操作共用这一把锁finaltransientReentrantLocklock=newReentrantLock();// 底层存储数组,volatile修饰,保证引用可见性privatetransientvolatileObject[]array;// 获取数组finalObject[]getArray(){returnarray;}// 设置数组引用finalvoidsetArray(Object[]a){array=a;}lock:ReentrantLock 可重入独占锁,写操作必须获取锁,防止多个写线程并发复制多个数组;
volatile Object[] array:volatile 保证数组引用修改对其他线程立即可见,数组内部元素不具备 volatile 可见性。
二、add () 方法源码
publicbooleanadd(Ee){finalReentrantLocklock=this.lock;lock.lock();// 加锁try{Object[]elements=getArray();intlen=elements.length;// 复制原数组到新数组,长度+1Object[]newElements=Arrays.copyOf(elements,len+1);newElements[len]=e;// 新元素放入末尾setArray(newElements);// 将array引用指向新数组returntrue;}finally{lock.unlock();// 释放锁,finally保证锁一定释放}}add () 作用
1.获取独占锁,阻止其他写线程并发修改;
2.获取当前底层数组,复制生成长度 + 1的全新数组;
3.在新数组尾部添加元素;
4.将成员变量 array 引用指向新数组;
5.finally 释放锁。
关键点:全程不修改原数组,原数组保持不变,读线程仍然读取旧数组。
三、remove (int index) 方法源码
publicEremove(intindex){finalReentrantLocklock=this.lock;lock.lock();try{Object[]elements=getArray();intlen=elements.length;EoldValue=get(elements,index);// 获取待删除旧值intnumMoved=len-index-1;Object[]newElements;if(numMoved==0)// 删除最后一个元素,复制长度len-1newElements=Arrays.copyOf(elements,len-1);else{newElements=newObject[len-1];// 分段拷贝:index前复制,index之后向前移动一位System.arraycopy(elements,0,newElements,0,index);System.arraycopy(elements,index+1,newElements,index,numMoved);}setArray(newElements);returnoldValue;}finally{lock.unlock();}}remove () 作用
1.加锁;
2.获取原数组,取出待删除位置元素;
3.创建长度len-1新数组;
4.通过System.arraycopy把原数组除 index 位置以外的数据拷贝到新数组;
5.array 引用切换到新数组;返回被删除元素;释放锁。
四、set (int index, E element) 方法源码
publicEset(intindex,Eelement){finalReentrantLocklock=this.lock;lock.lock();try{Object[]elements=getArray();EoldValue=get(elements,index);if(oldValue==element){// 新旧值相等,不需要复制数组,直接返回旧值returnoldValue;}Object[]newElements=Arrays.copyOf(elements,elements.length);newElements[index]=element;setArray(newElements);returnoldValue;}finally{lock.unlock();}}set () 作用
1.加锁;
2.获取原数组,取出 index 位置旧值;
3.如果新旧对象完全相同,无需复制数组,直接返回;
4.复制完整新数组,修改新数组指定下标;
5.替换 array 引用,返回旧值;释放锁。
五、get (int index) 方法源码
publicEget(intindex){returnget(getArray(),index);}privateEget(Object[]a,intindex){return(E)a[index];}get () 作用
读操作不加锁,直接获取当前volatile array引用,通过下标取值。
弱一致性:读取的是数组快照。线程读取期间,其他线程执行 add/remove/set,会生成新数组,但当前读线程仍然访问旧数组,无法实时感知最新写入的数据。
小结
1.写操作(add/set/remove):加ReentrantLock,拷贝新数组,修改新数组,切换引用;
2.读操作 get:无锁读取数组快照,读写互不阻塞;
3.优点:读性能高;缺点:写拷贝开销大、占用额外内存,只能保证弱一致性;
4.使用场景:读多写少,如本地配置缓存、事件订阅列表。