CopyOnWriteArrayList線程安全分析


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操作,一定要謹慎處理。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM