6 get
get(int index)
- 读指定位置元素
get(Object[] a, int index)
读时无需加锁,如果读时其它线程正在向ArrayList添加数据,读还是只会读到旧数据,因为写时并不会锁住旧的数组.
7 remove
7.1 指定索引删除
依旧三板斧:
- 加锁
- 根据删除索引的位置,进行不同策略拷贝
- 解锁
7.2 批量删除
并非直接对数组元素逐个删除,而先对数组值循环判断,将无需删除的数据放到临时数组,最后临时数组中的数据就是我们不需要删除的数据.
8 总结
CopyOnWrite 并发容器适用于读多写少的并发场景.CopyOnWrite容器有很多优点,但同时也存在问题,开发时候需要注意:
内存占用问题
写时,内存里会同时驻存两个对象的内存,旧对象和新写入对象(复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存).若这些对象占用内存较大,很可能造成频繁GC,应用响应时间也变长.
针对该问题,可通过压缩容器中元素,减少大对象的内存,或者直接不使用CopyOnWrite容器,而使用其他并发容器,如ConcurrentHashMap。
CopyOnWriteArrayList 之殇
再比如一段简单的非 DB操作的业务逻辑,时间消耗却超出预期时间,在修改数据时操作本地缓存比回写DB慢许多。原来是有人使用了CopyOnWriteArrayList缓存大量数据,而该业务场景下数据变化又很频繁。
CopyOnWriteArrayList虽然是一个线程安全版的ArrayList,但其每次修改数据时都会复制一份数据出来,所以只适用读多写少或无锁读场景。
所以一旦使用CopyOnWriteArrayList,一定是因为场景适宜而非炫技。
CopyOnWriteArrayList V.S 普通加锁ArrayList读写性能
- 测试并发写性能
- 测试结果:高并发写,CopyOnWriteArray比同步ArrayList慢百倍
- 测试并发读性能
- 测试结果:高并发读(100万次get操作),CopyOnWriteArray比同步ArrayList快24倍
- 高并发写时,CopyOnWriteArrayList为何这么慢呢?因为其每次add时,都用Arrays.copyOf创建新数组,频繁add时内存申请释放性能消耗大。
数据一致性问题
CopyOnWrite容器只能保证数据的最终一致性
,不能保证数据的实时一致性
,请酌情使用.