Java中的CopyOnWrite

什么是CopyOnWrite

CopyOnWrite(COW),写时复制。 其基本思路是,从一开始你们都在共享同一个内容,当某我的想要修改这个内容的时候,才会真正把内容Copy出去造成一个新的内容而后再改,这是一种延时懒惰策略。 通俗的理解是当咱们往一个CopyOnWrite容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,而后新的容器里添加元素,添加完元素以后,再将原容器的引用指向新的容器。这样作的好处是咱们能够对CopyOnWrite容器进行并发的读,而不须要加锁,由于当前容器不会添加任何元素。因此CopyOnWrite容器也是一种读写分离的思想,读和写不一样的容器。java

<!-- more -->编程

CopyOnWrite在Java中的应用

经过搜索咱们能够发现有三个结果(JDK1.8),分别是数组

  • CopyOnWriteArrayList
  • CopyOnWriteArraySet
  • CopyOnWriteMap

copyonwrite-search

其中CopyOnWriteMap时Sun公司本身的实现,并不属于JDK; 咱们就经过分析一下CopyOnWriteArrayList的源码看看和ArrayList的区别。安全

首先经过构造方法,咱们就会发现和平时的容器构造方法好像有点不同:没有生成指定容器大小的构造方法。 为何要这么作?若是不能生成指定指定大小的容器,add()方法确定会受到影响。多线程

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

果真,add()方法和ArrayList的add()方法不同:并发

  • 多了一把锁
  • 每次添加都要copy一次

为何要加锁? CopyOnWrite容器通常应用在多线程的条件下。若是不加锁,有五个线程同时添加元素,那么内存中就会用五份拷贝。这样不只不能保证多线程下的安全性也浪费了大量的内存空间。性能

为何每次添加元素都要copy呢? 要解释这个问题,咱们得要它的构造方法提及。经过前面的分析,咱们知道它的构造方法是不能构造指定大小的容器。来看一下默认构造方法:网站

public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

只是生成了一个大小为0的数组!结合另外两个构造方法能够发现,全部的构造方法保证数组中的每一个元素都不为null。我推断出这样作的缘由为了:this

  • 不浪费内存
  • 保证多线程下的安全性

由于不管是添加、删除仍是迭代操做,为了可以在多线程下保证安全性都是先对快照操做而后再替换掉原来的数组。如此以来,数组中空余的部分变得毫无用处并且还浪费了宝贵的内存空间!线程

CopyOnWrite的不足

CopyOnWrite有不少好处,例如在读多写少且对数据的一致性要求不是很高的时候能够考虑采用CopyOnWrite容器,应用场景如:网站黑名单,商品类目的存储,可是咱们也要注意到它不足的地方:

  • 内存占用问题 假如元素组有一百兆的数据,add一百兆的数据,这时候内存中就会存在三百兆数据。删除操做也存着相似问题

  • 数据的一致性 由于添加、删除都是先对快照进行操做,因此就有可能出现数据不一致的状况,编程的时候要注意到这一点!

  • 性能问题 添加操做会调用Arrays.copyOf(),拷贝整个数组;而删除操做更耗时,将数组中的元素一个一个拷贝。 解决办法是尽可能批量操做,避免单个操做。

经过分析,其实CopyOnWrite并非想象中的那么高深,理解它的思想分析起来就轻松啦~

参考

聊聊并发-Java中的Copy-On-Write容器

相关文章
相关标签/搜索