CopyOnWriteArrayList为什么不会产生ConcurrentModificationException?

场景

多线程场景下对未正确处理并发的容器进行迭代会抛出异常(ConcurrentModificationException),并发容器(CopyOnWriteArrayList)不会抛出ConcurrentModificationException,并且该类容器也不需要在客户端(调用者)进行加锁或复制。

分析

CopyOnWriteArrayList源码入手学习该类容器是如何避免ConcurrentModificationException问题的。

CopyOnWriteArrayList类声明了一个Object数组负责元素的存储,一个互斥锁(ReentrantLock)负责保障容器操作的线程安全性。

图示1

计算机生成了可选文字: pU山licclassCopyonUrlte人rrayLlst<E>刀呜笋le,几entsList<E>,Rando功Acce吕吕,Clonea为le,privatestaticfinallongse艺ia1V色z,王。刀UJ刀=java.10.Serlallzable{867326419574下942595L二/介介Thelockprotec七工nqall功utator3古/trd卫ISientfinalReentrantLOCk10Ck=ne纬守ReentrantLock()二/**Theprivatearray,accessedonlyvlagetArray/setArray.*/volatiletransientobject[]array二

 

一、请求在容器中添加新元素的实现机制

1、获取互斥锁

2Copy当前元素数组创建新数组,数组内存空间增1

3、添加元素

4、请求容器数据数组内存地址变更为新数组地址

5、释放互斥锁

6、返回结果

图示2

计算机生成了可选文字: /金含*Append3thespeci上ledele功enttotheendof七h13113t.*色para田几eele功enttobe色return<tt>true</t七>appendedltoth1sa曰3pecifiedbV115七{011nkCollection弃add}】含/pu山lichooiean画(E。)fin口1ReentrantLOCklock.lock();try((10Ck=this。10Ck二Okjject[]el巳功ent3=qetArray();Intlen=ele功en七s.lenq七h二C)bject[]neuElements=Arrays.copyof(ele功ent3,len+1);ne份Ele功ent3[len]=e二3etArray(ne盯Elements)Jreturntrue二}finally(loCk·unlock()二)

 

二、容器迭代

1、创建Iterator实现类(内部类)实例,传递容器当前数据数组,初始下标为0

2、返回Iterator实现类(内部类)实例

图示3.1

计算机生成了可选文字: /古金tReturnsaniteratorovertheele功entsInthls11stInpropersequence.t舍<p>Thereturnedlteratorprovlde3a3nap3ho七ofthe3ta七eo上七he*可henthelterator可asconstructed.Nosynchronlzatlon15needed古traver3ingtheiterator.Theiteratordoe吕<em>NOT</e功>吕uppor七古<七七》remove《/tt>拍e七hod.金115七份hlle七he金色returnaniteratorovertheele功ent3th13li3tInproper3equencet/pu劲110工terator<E>脸口国里口显()(returnn即COUlterator<E>(getArray(】

图示3.2

计算机生成了可选文字: privatestatioclassCOUlterator<E>】,甲le,nentsLls七I七erator<E>(/介古Snap3hotprivatefinalofthearraV介/Ob」ec七[]/**Indexa上elementtaprivateintcursor姿snapshot姿bereturnedby3证bsequentcalltonext.金/private(Ob〕ect[]elemen七s,intInl七la1Cursor)(cursor=In几七la1Cur3or二snapshot=ele功ent3二pu山licboole日口1ha5Next(){returncursor<snapshot.lenqth姿pU山110booleazlha3Prevlous(){retUtnCUrS0r>O二


并发场景下对容器的添加操作是通过在容器内部数据数组的副本来完成的。对容器的迭代使用的是容器原始数据数组因为迭代不会产生修改,因此多个线程可以同时对容器进行迭代,而不会对彼此干扰或影响修改容器的线程。

 

参考资料

1Java Concurrency in Practice

2JDK1.7#java.util.concurrent.CopyOnWriteArrayList<E>


Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐