CopyOnWriteArrayList源码阅读笔记

72 阅读3分钟

先从基础部分开始看起吧,但是常用集合的八股解释已经多如牛毛了,即使一行源码没看过,估计也能知道个七七八八,那么,稍微提高一点标准,比如,从一些线程安全的集合开始看起?

1.写时复制

CopyOnWriteArrayList就是通过写时复制来实现的,简单来说,读的时候随便读,写的时候先开辟一块新的内存,然后替换掉原来的数组。如果是看了一些八股文的话,可能最后只能记住这么一句话,但是具体怎么写的,怎么复制的呢?是通过synchronizied还是lock上锁呢?先说答案,我看的是1.8版本,用的是ReentrantLock。

接下来我们来看看源码:

2.类的声明

1746003298867.png 实现了一下==以下接口:

  1. List 不用解释了吧?
  2. RandomAccess 支持随机
  3. Cloneable 支持拷贝
  4. java.io.Serializable 支持序列化 当然,这些不需要去记住,只作为了解即可,接下来我们要看的才是重要的:看内部的成员变量、看构造函数、再看几个主要的方法

3.内部成员

1746003790939.png

看到了吧,new了一个lock出来,并且用final修饰,表示不可更改。用了transient修饰,表示不被序列化。 另外一个属性就是一个对象数组了,用了volatile修饰,多线程嘛,可见性嘛,禁止指令重排序嘛。。。

4.构造函数

1746004222878.png

无参构造很简单,设置一个数组就好。 有参构造,则要先进行判断,看一下入参是不是已经是CopyOnWriteArrayList,如果是则直接赋值引用即可。不是的话就用数组拷贝一下内部元素。 我看到这里有点疑惑,为什么是用了引用,而不是创建一份新的数组呢?这样一旦修改不就会导致原来的集合数组发生变化吗?后来一想,哦,对!写时复制呀,每次写都会发生数组迁移,没错,是每次,每一次!

5.get和set

image.png 重点来了,读时是不需要加锁的直接返回内部数组相对应的位置。但是写的时候就不行了,写的时候要先获取锁,并且不管写入的值是否与原来的值相等,都需要setArray()。那么问题来了。。。

1.值发生了变化时,不可以在原来的数组上写吗? 不能,因为读的时候是没加锁的,这么做可能会导致另一个读线程发生脏读。

2.值没发生变化为什么也要setArray呢? 还记得Object数组的volatile修饰符嘛?这是为了锁内所有修改对其他线程立即可见。防止指令重排序导致意外行为。 总之,都是为了线程安全

5.add操作

1746007872559.png

再看一下Add操作,每次增加都需要新开辟一块空间,而仅仅是为了增加一个元素的位置,这也就是说为什么它适合读多写少的场景了。并发写的情况下,需要依次来获取锁,每次写都要发生地址变动。

6.clone操作

1746008116829.png 最后,再来看下clone操作吧,其实只是调用了父类的clone对吧。但是这里的clone.resetLock()是什么意思呢?

1746008475774.png 貌似是闭源的,但是看到别人通过反编译也能看到代码。不过理解一下这里的意思应该就是刷新一下克隆对象的锁,毕竟你都复制了一个新对象了,俩对象的修改不能共用一把锁吧,很好理解。

其他的方法大家再去自行了解吧。。。