CopyOnWriteArrayList是開發過程中常用的一種並發容器,多用於讀多寫少的並發場景。但是CopyOnWriteArrayList真的能做到完全的線程安全嗎? 答案是並不能。
一、CopyOnWriteArrayList原理
我們可以看出當我們向容器添加或刪除元素的時候,不直接往當前容器添加刪除,而是先將當前容器進行Copy,復制出一個新的容器,然后新的容器里添加刪除元素,添加刪除完元素之后,再將原容器的引用指向新的容器,整個過程加鎖,保證了寫的線程安全。
1 public boolean add(E e) { 2 synchronized (lock) { 3 Object[] elements = getArray(); 4 int len = elements.length; 5 Object[] newElements = Arrays.copyOf(elements, len + 1); 6 newElements[len] = e; 7 setArray(newElements); 8 return true; 9 } 10 } 11 12 public E remove(int index) { 13 synchronized (lock) { 14 Object[] elements = getArray(); 15 int len = elements.length; 16 E oldValue = get(elements, index); 17 int numMoved = len - index - 1; 18 if (numMoved == 0) 19 setArray(Arrays.copyOf(elements, len - 1)); 20 else { 21 Object[] newElements = new Object[len - 1]; 22 System.arraycopy(elements, 0, newElements, 0, index); 23 System.arraycopy(elements, index + 1, newElements, index, 24 numMoved); 25 setArray(newElements); 26 } 27 return oldValue; 28 } 29 }
而因為寫操作的時候不會對當前容器做任何處理,所以我們可以對容器進行並發的讀,而不需要加鎖,也就是讀寫分離。
1 public E get(int index) { 2 return get(getArray(), index); 3 }
一般來講我們使用時,會用一個線程向容器中添加元素,一個線程來讀取元素,而讀取的操作往往更加頻繁。寫操作加鎖保證了線程安全,讀寫分離保證了讀操作的效率,簡直完美。
二、數組越界
但想象一下如果這時候有第三個線程進行刪除元素操作,讀線程去讀取容器中最后一個元素,讀之前的時候容器大小為i,當去讀的時候刪除線程突然刪除了一個元素,這個時候容器大小變為了i-1,讀線程仍然去讀取第i個元素,這時候就會發生數組越界。
測試一下,首先向CopyOnWriteArrayList里面塞10000個測試數據,啟動兩個線程,一個不斷的刪除元素,一個不斷的讀取容器中最后一個數據。
1 public void test(){ 2 for(int i = 0; i<10000; i++){ 3 list.add("string" + i); 4 } 5 6 new Thread(new Runnable() { 7 @Override 8 public void run() { 9 while (true) { 10 if (list.size() > 0) { 11 String content = list.get(list.size() - 1); 12 }else { 13 break; 14 } 15 } 16 } 17 }).start(); 18 19 new Thread(new Runnable() { 20 @Override 21 public void run() { 22 while (true) { 23 if(list.size() <= 0){ 24 break; 25 } 26 list.remove(0); 27 try { 28 Thread.sleep(10); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 } 33 } 34 }).start(); 35 }
運行,可以看出刪除到第7個元素的時候就發生了數組越界:

從上可以看出CopyOnWriteArrayList並不是完全意義上的線程安全,如果涉及到remove操作,一定要謹慎處理。
