Java集合框架源码研读-CopyOnWriteArrayList

Posted by AlstonWilliams on February 17, 2019

今天我们再来研究一个List-CopyOnWriteArrayList

我们首先从类注释来大致了解一下这个类:

从中我们可以提取出来几点关键点:

  • 它先当于一个线程安全的ArrayList
  • 向其中添加数据或者删除数据的操作,都是线程安全的
  • 它在添加数据或者删除数据方面效率有点低
  • Iterator不会反应出新增加的元素

在接下来的文章中,我们将会回答以下几个问题:

  • 它是如何实现的?
  • 为什么它是线程安全的
  • 为什么在增加数据时,它效率很低?
  • 它跟ArrayList有什么异同?
  • 它适应于什么场景?

解答

它是如何实现的?

它的实现方式,跟ArrayList基本上一致,它的底层数据结构,也是一个数组,区别就是,增加了一个锁来保证线程安全.

为什么它是线程安全的?

CopyOnWriteArrayList增加了一个锁,一个可重入锁ReentrantLock,就是在进行写操作时,通过这个锁来保证线程安全性.

另外,在执行写操作的时候,会创建一个新的数组,这个数组是原数组的拷贝,然后它会对这个新数组执行写操作,在写操作完成之后,会将原先的指向旧数组的引用,让其重新指向新数组.这就保证了用户在更新数据的过程中,不会看到脏数据.

我们看一下set(int index, E element)的实现,就能明白上面所述了.

为什么在增加数据时,它效率很低?

我们先贴出add(E e)方法的源码:

这里我们可以看到,当新增数据的时候,会创建一个长度为原数组长度+1的新数组,将原数组中的数据拷贝过去,然后将新数组的最后一位设置为想要添加的数据.

对比ArrayList的实现,我们可以发现,这里由于频繁的创建新数组,移动元素,便会造成效率低.

它跟ArrayList有什么异同?

相同点是,底层数据结构都是数组.

不同点是,它引入了一个可重入锁来保证线程安全,另外,它不跟ArrayList一样,在增加数据时,如果容量不够了,就按照一个公式进行扩容,而是每次增加数据时,如果容量不够了,只扩容一位.

另外,它不是Fail-Fast的,即,在使用Iterator进行迭代时,如果底层数组发生了变化,不会抛出ConcurrentModificationException.

它适用于什么场景?

由于其在修改数据时,效率偏低,所以,它主要适用于读多写少,并且数据容器需要是线程安全的场景.

总结

其实,Java中很多源码的实现都是差不多的.就拿我们现在介绍的集合框架来说吧,几个List的实现基本上都差不多,最多就是底层的数据结构换了一下,而底层数据结构其实就那么多,数组,链表,树,虽然链表和树又可以细分为很多种类,但是,整体来说,整个集合框架,同类的集合,还是大同小异的.

另外,同样是集合框架中的一部分,有的集合框架就依赖于其他的集合框架,比如Set的实现,基本上就是依赖于对应的Map的实现.

因为它们基本上都是相同的,所以我们读起来会很简单.但是,我们在读源码时,除了了解其实现原理,还要有其他的一系列目标,比如,对比各个集合框架之间的异同,其各个操作的时间复杂度,适合在什么场景使用.

读源码时,除了其大体轮廓,很多时候,我们还要注意其实现的细节,因为很多同类源码,在大体轮廓上都是相同的,只有在实现细节上有差异.注意与其他同类源码对比,找出其最适合的场景.