java并发编程--并发容器CopyOnWriteArrayList

概述

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

原理

我们通过它的实现源码来解读

public boolean add(E e) {
    //1、先加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        //2、拷贝数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //3、将元素加入到新数组中
        newElements[len] = e;
        //4、将array引用指向到新数组
        setArray(newElements);
        return true;
    } finally {
       //5、解锁
        lock.unlock();
    }
}

可见写的时候首先要获取锁,保证只有一个线程在进行写操作,防止并发

原理就是拷贝一份新的内存区域,在新的区域对数组进行修改,然后让之前的数组指针指向新的数组内存区域

由于所有的写操作都是在新数组进行的,这个时候如果有线程并发的写,则通过锁来控制,如果有线程并发的读,则分几种情况:
1、如果写操作未完成,那么直接读取原数组的数据;
2、如果写操作完成,但是引用还未指向新数组,那么也是读取原数组数据;
3、如果写操作完成,并且引用已经指向了新的数组,那么直接从新数组中读取数据。

可见,CopyOnWriteArrayList的读操作是可以不用加锁的。

我们通过一个例子来说明一下:

public class Test {

    public static void main(String[] args) {

        List<Integer> list = Arrays.asList(new Integer[] { 1, 2, 3, 4 });

        CopyOnWriteArrayList<Integer> copyList = new CopyOnWriteArrayList<Integer>(list);

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.execute(new ReadList(copyList));
        executorService.execute(new ReadList(copyList));
        executorService.execute(new WriteList(copyList));
        executorService.execute(new ReadList(copyList));
        executorService.execute(new WriteList(copyList));
        executorService.execute(new ReadList(copyList));
        executorService.execute(new ReadList(copyList));

        executorService.shutdown();

    }

}

class ReadList implements Runnable {

    private List<Integer> list;

    public ReadList(List<Integer> list) {

        this.list = list;
    }

    /** 
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run() {

        for (int i : list) {

            System.out.println("读取list, i = " + i);
        }

    }
}

class WriteList implements Runnable {

    private List<Integer> list;

    public WriteList(List<Integer> list) {

        this.list = list;
    }

    /** 
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run() {

        for (int i = 0; i < 5; i++) {
            list.add(10);
            System.out.println("写list");

        }
    }

}

通常情况下,当你对一个集合既有写操作也有读操作的时候,就会报:java.util.ConcurrentModificationException 的错误

可见通过CopyOnWriteArrayList是线程安全的处理方式

使用场景

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

缺陷

  • 内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。

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

透露的思想

CopyOnWriteArrayList表达的一些思想:
1、读写分离,读和写分开
2、最终一致性
3、使用另外开辟空间的思路,来解决并发冲突

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值