先寫一段代碼證實CopyOnWriteArrayList確實是線程安全的。java
ReadThread.java數組
import java.util.List; public class ReadThread implements Runnable { private List<Integer> list; public ReadThread(List<Integer> list) { this.list = list; } @Override public void run() { for (Integer ele : list) { System.out.println("ReadThread:"+ele); } } }
WriteThread.java安全
import java.util.List; public class WriteThread implements Runnable { private List<Integer> list; public WriteThread(List<Integer> list) { this.list = list; } @Override public void run() { this.list.add(9); } }
測試程序:TestCopyOnWriteArrayList.java多線程
import java.util.Arrays; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestCopyOnWriteArrayList { private void test() { //一、初始化CopyOnWriteArrayList List<Integer> tempList = Arrays.asList(new Integer [] {1,2}); CopyOnWriteArrayList<Integer> copyList = new CopyOnWriteArrayList<>(tempList); //二、模擬多線程對list進行讀和寫 ExecutorService executorService = Executors.newFixedThreadPool(10); executorService.execute(new ReadThread(copyList)); executorService.execute(new WriteThread(copyList)); executorService.execute(new WriteThread(copyList)); executorService.execute(new WriteThread(copyList)); executorService.execute(new ReadThread(copyList)); executorService.execute(new WriteThread(copyList)); executorService.execute(new ReadThread(copyList)); executorService.execute(new WriteThread(copyList)); System.out.println("copyList size:"+copyList.size()); } public static void main(String[] args) { new TestCopyOnWriteArrayList().test(); } }
運行上面的代碼,沒有報出併發
java.util.ConcurrentModificationException
說明了CopyOnWriteArrayList併發多線程的環境下,仍然能很好的工做。ide
CopyOnWriteArrayList使用了一種叫寫時複製的方法,當有新元素添加到CopyOnWriteArrayList時,先從原有的數組中拷貝一份出來,而後在新的數組作寫操做,寫完以後,再將原來的數組引用指向到新數組。性能
當有新元素加入的時候,以下圖,建立新數組,並往新數組中加入一個新元素,這個時候,array這個引用仍然是指向原數組的。測試
當元素在新數組添加成功後,將array這個引用指向新數組。this
CopyOnWriteArrayList的整個add操做都是在鎖的保護下進行的。 這樣作是爲了不在多線程併發add的時候,複製出多個副本出來,把數據搞亂了,致使最終的數組數據不是咱們指望的。線程
CopyOnWriteArrayList的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; //四、將array引用指向到新數組 setArray(newElements); return true; } finally { //五、解鎖 lock.unlock(); } }
因爲全部的寫操做都是在新數組進行的,這個時候若是有線程併發的寫,則經過鎖來控制,若是有線程併發的讀,則分幾種狀況:
可見,CopyOnWriteArrayList的讀操做是能夠不用加鎖的。
經過上面的分析,CopyOnWriteArrayList 有幾個缺點:
CopyOnWriteArrayList 合適讀多寫少的場景,不過這類慎用
由於誰也無法保證CopyOnWriteArrayList 到底要放置多少數據,萬一數據稍微有點多,每次add/set都要從新複製數組,這個代價實在過高昂了。在高性能的互聯網應用中,這種操做分分鐘引發故障。
#CopyOnWriteArrayList透露的思想 如上面的分析CopyOnWriteArrayList表達的一些思想: