CopyOnWriteArrayList源码解析
文章目录
- CopyOnWriteArrayList源码解析
- 一、CopyOnWriteArrayList
- 二、总结
一、CopyOnWriteArrayList
在 JUC
中,对于 ArrayList
的线程安全用法,比较推崇于使用 CopyOnWriteArrayList
,那么CopyOnWriteArrayList
是怎么解决线程安全问题的呢,本文通过解析 CopyOnWriteArrayList
的源码,主要对几个常用的函数进行讲解。
在进行 CopyOnWriteArrayList
的源码讲解之前,先看下同样实现了线程安全的 Vector
,之所以不推荐使用 Vector
,主要是性能太差了,可以看下 Vector
的 add()
和 get()
的源码:
可以看到Vector
的添加和读取操作都被加上了 synchronized
锁,当并发情况下,因为锁的存在相当于变成了单线程的操作,所以效率肯定低,同样这样的优点就是保证了数据的唯一性,不会读取到脏数据。
下面再看下 CopyOnWriteArrayList
是如何解决并发问题的呢。
首先看下 CopyOnWriteArrayList
的全局变量有哪些:
其中 lock
锁就是每次在做写操作时,锁的句柄,array
就是具体存储数据的数组,注意这里的 array
被 volatile
所修饰,因此可以在并发情况下实现数据的可见性。
当 new
创建了一个 CopyOnWriteArrayList
时,如果是使用无参的构造函数,则将 array
的长度默认成 0
,创建了一个空的数组。
在使用 add()
添加数据时,先使用 lock.lock()
上锁,并获取到当前的 array
数组,然后对 array
进行 copyOf()
,新的数组的长度是之前的长度 +1
,这样才能存放当前新的值,将新值填充后,再替换掉旧的 array
数组后,释放当前锁。
在使用 get()
获取指定下边数据时,直接对当前的 array
进行操作:
在进行 remove()
删除时,先使用 lock.lock()
上锁,然后再获取当前的 array
数组,如果传入的 index
正好是最后一个,那么 numMoved
计算出来就是 0
,则使用 copyOf()
,长度进行 -1
去除最后一个数据。否则传入的不是最后一个,先声明一个新的 array
数组,数组的长度就是旧的 array
的 len - 1
,再将 0
到 index
的数据 arraycopy()
至新的 array
数组,然后再将 index + 1
后的再 arraycopy()
至新的 array
数组,最后将新的 array
数组替换旧的,然后释放锁。
二、总结
- 当
new
新建一个CopyOnWriteArrayList
后会生成一个数组array
来存放添加的内容,如果是无参的构造函数,则array
的长度为0
,添加数据时再进行扩容。同时会声明一个ReentrantLock
锁。 - 当进行
add()
操作时,先进行上锁,然后对当前的array
进行copyOf()
,并且新的长度是之前的长度+1
,这样才能存放当前新的值,将新值填充后,再替换掉旧的array
数组后,释放当前锁。 - 当使用
get()
获取数据时,无需上锁,直接读取当前array
数组的指定位置。 - 当使用
remove()
时,同样先进行上锁,然后再获取当前的array
数组,如果传入的index
正好是最后一个,则使用copyOf()
,长度进行-1
,否则的话先声明一个新的array
数组,现将0
到index
的数据arraycopy()
至新的array
数组,然后再将index + 1
后的再arraycopy()
至新的array
数组,最后将新的array
数组替换旧的,然后释放锁。
读下来之后可以感觉出来CopyOnWriteArrayList
的源码非常容易理解和阅读,同时也可以看出来,CopyOnWriteArrayList
实现了写写隔离,但读读是可以共享的,这就有可能出现当某个数据再修改时,读进行了操作,导致读取到的还是旧的数据。还有就是每次写操作都对数组进行Copy
,假如数据量非常大的情况下,进行Copy
消耗的资源则会进行x 2
,因此使用CopyOnWriteArrayList
时,需要考虑下自己的数据量以及读写的频次。