CopyOnWriteList简介
ArrayList是线程不安全的,于是JDK在java.util.concurrent包下新增了一个线程并发安全的List-CopyOnWriteList,中心思想就是copy-on-write。简单来说就是读写分离,读时共享,写时复制(原本的array)更新(且为独占式的加锁),而我们下面分析的源码实现就是这个思想的体现。

成员属性:

一个final锁对象lock
使用volatile修饰的array,保证写线程更新array之后别的线程能够看到更新后的array,但是不能保证实时性:在数组副本上添加元素之后,还没有更新array指向新地址之前,别的线程看到的还是旧的array。
后面一个是获取数组,一个是设置数组。
构造方法:

无参构造方法就是创建一个新的长度为0的object数组,然后调用setArray方法将其设置给CopyOnWriteList的成员变量array。
添加元素add(E e)

修改元素set(int index,E element)

删除元素remove(int index)

获取元素get(i)
使用get(i)可以获取指定位置i的元素



1 获取array数组这里的get方法也体现了copy-on-write-list的弱一致性问题。
2 访问传入入参下标的元素
我们看到get过程是没有加锁的,假设threadA执行1之后2之前,这时恰好threadB执行remove操作,threadB或获取独占锁,然后执行写时复制操作,即复制一个新的数组newArray,然后在newArray中执行删除操作,更新array。如果在执行setArray之前ThreadA抢到CPU时间片继续执行代码,那么此时get还是从旧数组里面取得元素此时元素可能已经被删除了,所以这就是弱一致性问题。但却保证了最终一致性。

适用场景:
读操作可以尽可能的快,而写即使慢一些也没关系,在很多 应用场景中,读操作可能会远远多于写操作。黑名单是最典型的场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索,这些不能被搜索的关键字会被放在一个黑名单中,黑名单并不需要实时更新,可能一段时间更新一次就行了,用户每天多次搜索时,会检查当前关键字在不在黑名单中,如果在,则不能搜索,这种读多写少的场景也很适合使用CopyOnWrite集合。
读写规则:
读写锁的思想是:读读共享,写写互斥,读写互斥,写读互斥,原因是由于读操作不会修改原有的数据,因此并发读并不会有安全问题;而写操作是不安全的,所以当写操作时,不允许有读操作加入,也不允许第二个写线程加入。
为了将读取的性能发挥到极致,CopyOnWriteArrayList读取是完全不加锁的,写入也不会阻塞读取操作,也就是你可以在写入的同时进行读取,只有写入和写入之间需要进行同步,也就是不允许多个写入同时发生,但是在写入时允许读取同时发生,这样一来,读操作的性能就会大幅度提升。
CopyOnWriteArrayList思想:
我们可以这样打个比方,比如我在写一篇简书或者是修改一篇简书文章,其实我正在修改只是没有点击保存重新发布,所以别人看到的就是我之前没有修改的那一篇(旧数据)。只要这时我写完了修改完了。点击保存(替换地址),而这时你们看到的就是我新修改完后的简书文章了。
因为无计其数的读者可能正在读我那一篇简书文章,他们之间肯定是不可以阻塞的,假如阻塞的话,那么就要面临排队读博客了。而且还要保证我修改简书文章的时候,读者还能读到简书内容,这时候就需要读写分离(读与写同时并发,互不影响),即达到写有锁,读无锁,读写直接不阻塞的效果。
从CopyOnWriteArrayList的名字就可以看出来意思就是,当容器需要被修改的时候,不直接修改当前容器,而是先将当前容器进行copy,复制出一个新的容器,然后修改新的容器,完成修改之后,再将原容器的引用指向新的容器,这样就完成了整个修改过程。
这样做的好处就是,它利用了 不变性 原理,因为容器每次修改都是创建新副本,所以对于旧容器来说,其实是不可变的,也是线程安全的,无需进一步的同步操作。我们也可以并发地读,而不需要加锁,因为当前容器不会添加任何元素,也不会有修改。
CopyOnWriteArrayList的所有操作(add,set,remove等)都是通过创建底层数组的新副本来实现的,所以CopyOnWrite容器也是一种读写分离的思想体现,读和写使用不同的容器。
缺点:
因为使用了写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,这一点会占用额外的内存空间。
在元素较多或复杂的情况下,复制的开销也很大。
复制过程不仅会占用多的内存,还会消耗CPU资源,会降低整体性能。
数据一致性问题:
由于CopyOnWrite容器的修改是先修改副本,所以这次修改对于其他线程来说,并不是实时能看到的,只有在修改完成之后才能体现出来。如果你希望写入的数据马上被其他线程看到的话,CopyOnWrite容器不适合你。
网友评论