Java中的CopyOnWrite
2019獨角獸企業重金招聘Python工程師標準>>>
什么是CopyOnWrite
CopyOnWrite(COW),寫時復制。 其基本思路是,從一開始大家都在共享同一個內容,當某個人想要修改這個內容的時候,才會真正把內容Copy出去形成一個新的內容然后再改,這是一種延時懶惰策略。 通俗的理解是當我們往一個CopyOnWrite容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,復制出一個新的容器,然后新的容器里添加元素,添加完元素之后,再將原容器的引用指向新的容器。這樣做的好處是我們可以對CopyOnWrite容器進行并發的讀,而不需要加鎖,因為當前容器不會添加任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。
<!-- more -->
CopyOnWrite在Java中的應用
通過搜索我們可以發現有三個結果(JDK1.8),分別是
- CopyOnWriteArrayList
- CopyOnWriteArraySet
- CopyOnWriteMap
其中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。我推斷出這樣做的原因為了:
- 不浪費內存
- 保證多線程下的安全性
因為無論是添加、刪除還是迭代操作,為了能夠在多線程下保證安全性都是先對快照操作然后再替換掉原來的數組。如此以來,數組中空余的部分變得毫無用處而且還浪費了寶貴的內存空間!
CopyOnWrite的不足
CopyOnWrite有很多好處,例如在讀多寫少且對數據的一致性要求不是很高的時候可以考慮采用CopyOnWrite容器,應用場景如:網站黑名單,商品類目的存儲,但是我們也要注意到它不足的地方:
-
內存占用問題 假如元素組有一百兆的數據,add一百兆的數據,這時候內存中就會存在三百兆數據。刪除操作也存著類似問題
-
數據的一致性 因為添加、刪除都是先對快照進行操作,所以就有可能出現數據不一致的情況,編程的時候要注意到這一點!
-
性能問題 添加操作會調用Arrays.copyOf(),拷貝整個數組;而刪除操作更耗時,將數組中的元素一個一個拷貝。 解決辦法是盡量批量操作,避免單個操作。
通過分析,其實CopyOnWrite并不是想象中的那么高深,理解它的思想分析起來就輕松啦~
參考
聊聊并發-Java中的Copy-On-Write容器
轉載于:https://my.oschina.net/liuxiaomian/blog/748161
總結
以上是生活随笔為你收集整理的Java中的CopyOnWrite的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ubuntu 12.04 安装g++ a
- 下一篇: bzoj1230[Usaco2008 N