diff --git a/blogs/Java/口水话/集合源码.md b/blogs/Java/口水话/集合源码.md index ba72351..f327980 100644 --- a/blogs/Java/口水话/集合源码.md +++ b/blogs/Java/口水话/集合源码.md @@ -115,14 +115,18 @@ TreeSet 基本上不常用,一般需要不重复元素且能根据元素进行 #### CopyOnWriteArrayList -CopyOnWriteArrayList 是一个线程安全的 ArrayList,它是通过 synchronized + 数组拷贝 + volatile 关键字保证了线程安全。在它 add value 时,是先 synchronized 加锁保证同一时刻只有一个线程执行 add 方法,再拷贝一份新数组,把 value 加到新数组里面,最后再把新数组赋值给原数组。你可能会问了,这都加锁了,为啥不在原数组上面直接操作呢?原因主要有两个: +CopyOnWriteArrayList 是一个线程安全的 ArrayList,使用了写时复制策略,它是通过 synchronized + 数组拷贝 + volatile 关键字保证了线程安全。在它 add value 时,是先 synchronized 加锁保证同一时刻只有一个线程执行 add 方法,再拷贝一份长度加一的新数组,把 value 加到新数组的末尾,最后再把新数组赋值给原数组。你可能会问了,这都加锁了,为啥不在原数组上面直接操作呢?原因主要有两个: -1. volatile 关键字修饰的是数组,如果只是简单的在原数组上修改其中某几个元素的值,是无法触发可见性的,我们必须通过修改数组的内存地址才行,也就说要对数组进行重新赋值才行 2. 在新的数组上进行拷贝,对老数组没有任何影响,只有新数组完全拷贝完成之后,外部才能访问到,降低了在赋值过程中,老数组数据变动的影响 +2. volatile 关键字修饰的是数组,如果只是简单的在原数组上修改其中某几个元素的值,是无法触发可见性的,我们必须通过修改数组的内存地址才行,也就说要对数组进行重新赋值才行 + +数组拷贝也有一点好处,那就是没有空间损耗,元素是占满数组的。 remove 时和 add 相似,就不多说了。 -CopyOnWriteArrayList 读的时候不需要加锁,适合读多写少的场景,但是每次变动数组,都会拷贝一份新数组,可能会加重 GC。 +CopyOnWriteArrayList 读的时候不需要加锁,适合读多写少的场景,这同时也会带来一个弱一致性的问题,即获取迭代器后迭代元素,其他线程对该 list 进行的增删改不可见,因为它们操作的是两个不同的数组,这就是弱一致性。简单来说,就是 CopyOnWriteArrayList 提供了弱一致性的迭代器,从而保证获取迭代器后,其他线程对 list 的修改是不可见的,迭代器遍历的数组是一个快照。 + +CopyOnWriteArraySet 的内部实现就是在其构造方法中 new CopyOnWriteArrayList 来实现的。 #### ConcurrentHashMap