CopyOnwriteArrayList原理和使用场景


以前很少用list 的remove 构造方法。直到今天…
场景:ArrayList在并发读写时,会抛出异常。
java中,List在遍历的时候,如果被修改了会抛出java.util.ConcurrentModificationException错误。

1.传统的list remove造成的问题

集合遍历是使用Iterator, Iterator是工作在一个独立的线程中,并且拥有一个互斥锁。Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast原则 Iterator 会马上抛出java.util.ConcurrentModificationException 异常。所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。
最大的原因是:主线程在遍历list的时候,子线程在向list中添加元素。

2.使用CopyOnWriteArrayList解决

CopyOnWriteArrayList类最大的特点就是,在对其实例进行修改操作(add/remove等)会新建一个数据并修改,修改完毕之后,再将原来的引用指向新的数组。这样,修改过程没有修改原来的数组。也就没有了ConcurrentModificationException错误。

3.使用方法

public class Resource3 {

public static void main(String[] args) throws InterruptedException {
    List<String> a = new ArrayList<>();
    a.add("a");
    a.add("b");
    a.add("c");

    final List<String> list = new CopyOnWriteArrayList<>(a);
    Thread t = new Thread(new Runnable() {
        int count = -1;

        @Override
        public void run() {
            while (true) {
                list.add(count++ + "");
            }
        }
    });

    t.setDaemon(true);
    t.start();
    Thread.sleep(3);
    for (String s : list) {
        System.out.println(s);
    }
}

}

4.CopyOnWrite是什么??

CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器

5.CopyOnWriteArrayList的使用场景

CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当关键字在不在黑名单当中,如果在,则提示不能搜索。实现代码如下:

6.CopyOnWrite的缺点:

CopyOnWrite容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。所以在开发时需要注意一下。

内存占用问题 在为CopyOnWrite的写时复制机制,所以在进行与操作的时候,内存里会同时驻扎两个对象内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加容器里,而旧容器的对象还在使用,所以有两个对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,就用响应时间也随这变长。

针对内存占用问题,可以通过压缩容器的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。

数据一致性问题 CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的数据,马上能读到,请不要使用CopyOnWrite容器。

补充

CopyOnWriteArrayList读取时不加锁,只是写入、删除、修改时加锁,所以一个线程X读取的时候另一个线程Y可能执行remove操作。remove操作首先要获取独占锁,然后进行写时复制操作,就是复制一份当前的array数组,然后在复制的新数组里面删除线程X通过get访问的元素,比如:1。删除完成后让array指向这个新的数组。

在线程x执行get操作的时候并不是直接通过全局array访问数组元素而是通过方法的形参a访问的,a指向的地址和array指向的地址在调用get方法的那一刻是一样的,都指向了堆内存的数组对象。之后改变array指向的地址并不影响get的访问,因为在调用get方法的那一刻形参a指向的内存地址就已经确定了,不会改变。所以读的仍然是旧数组

CopyOnWriteArrayList 与 Vector 的区别

Vector 是【绝对】线程安全的,CopyOnWriteArrayList 只能保证读线程会读到【已完成】的写结果,但无法像 Vector一样实现读操作的【等待写操作完成后再读最新值】的能力

CopyOnWriteArrayList 读性能远高于 Vector,并发线程越多优势越明显

CopyOnWriteArrayList 占用更多的内存空间

Vector 不能避免 ConcurrentModificationException 问题,而 CopyOnWriteArrayList 不存在这个问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值