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容器

相關文章
相關標籤/搜索