CopyOnWriteArrayList詳解

雲驛站發表於2014-04-04

發表於5個月前(2013-10-08 11:54)   閱讀(519) | 評論(0
8人收藏此文章,
我要收藏
贊0

    CopyOnWriteArrayList是ArrayList 的一個執行緒安全的變體,其中所有可變操作(add、set等等)都是通過對底層陣列進行一次新的複製來實現的。

     這一般需要很大的開銷,但是當遍歷操作的數量大大超過可變操作的數量時,這種方法可能比其他替代方法 有效。在不能或不想進行同步遍歷,但又需要從併發執行緒中排除衝突時,它也很有用。“快照”風格的迭代器方法在建立迭代器時使用了對陣列狀態的引用。此陣列在迭代器的生存期內不會更改,因此不可能發生衝突,並且迭代器保證不會丟擲ConcurrentModificationException。建立迭代器以後,迭代器就不會反映列表的新增、移除或者更改。在迭代器上進行的元素更改操作(remove、set和add)不受支援。這些方法將丟擲UnsupportedOperationException。允許使用所有元素,包括null。

    記憶體一致性效果:當存在其他併發 collection 時,將物件放入CopyOnWriteArrayList之前的執行緒中的操作
happen-before 隨後通過另一執行緒從CopyOnWriteArrayList中訪問或移除該元素的操作。 

   這種情況一般在多執行緒操作時,一個執行緒對list進行修改。一個執行緒對list進行fore時會出現java.util.ConcurrentModificationException錯誤。

   下面來看一個列子:兩個執行緒一個執行緒fore一個執行緒修改list的值。

01 package
com.lucky.concurrent.list;
02  
03 import
java.util.ArrayList;
04 import
java.util.List;
05 import
java.util.concurrent.ExecutorService;
06 import
java.util.concurrent.Executors;
07  
08 public
class
CopyOnWriteArrayListDemo {
09     /**
10      * 讀執行緒
11      * @author wangjie
12      *
13      */
14     private
static class
ReadTask
implements
Runnable {
15         List<String> list;
16  
17         public
ReadTask(List<String> list) {
18             this.list = list;
19         }
20  
21         public
void run() {
22             for
(String str : list) {
23                 System.out.println(str);
24             }
25         }
26     }
27     /**
28      * 寫執行緒
29      * @author wangjie
30      *
31      */
32     private
static class
WriteTask
implements
Runnable {
33         List<String> list;
34         int
index;
35  
36         public
WriteTask(List<String> list, int
index) {
37             this.list = list;
38             this.index = index;
39         }
40  
41         public
void run() {
42             list.remove(index);
43             list.add(index,
"write_" + index);
44         }
45     }
46  
47     public
void run() {
48         final
int NUM = 10;
49         List<String> list =
new ArrayList<String>();
50         for
(int
i =
0; i < NUM; i++) {
51             list.add("main_"
+ i);
52         }
53         ExecutorService executorService = Executors.newFixedThreadPool(NUM);
54         for
(int
i =
0; i < NUM; i++) {
55             executorService.execute(new
ReadTask(list));
56             executorService.execute(new
WriteTask(list, i));
57         }
58         executorService.shutdown();
59     }
60  
61     public
static void
main(String[] args) {
62         new
CopyOnWriteArrayListDemo().run();
63     }
64 }

執行結果:

從結果中可以看出來。在多執行緒情況下報錯。其原因就是多執行緒操作結果:那這個種方案不行我們就換個方案。用jdk自帶的類CopyOnWriteArrayList來做容器。這個類和ArrayList最大的區別就是add(E) 的時候。容器會自動copy一份出來然後再尾部add(E)。看原始碼:

01 /**
02     * Appends the specified element to the end of this list.
03     *
04     * @param e element to be appended to this list
05     * @return <tt>true</tt> (as specified by {@link Collection#add})
06     */
07    public
boolean add(E e) {
08    final
ReentrantLock lock = this.lock;
09    lock.lock();
10    try
{
11        Object[] elements = getArray();
12        int
len = elements.length;
13        Object[] newElements = Arrays.copyOf(elements, len +
1);
14        newElements[len] = e;
15        setArray(newElements);
16        return
true;
17    }
finally {
18        lock.unlock();
19    }
20    }

用到了Arrays.copyOf 方法。這樣導致每次操作的都不是同一個引用。也就不會出現java.util.ConcurrentModificationException錯誤。
換了種方案看程式碼:

1 //      List<String> list = new ArrayList<String>();
2         CopyOnWriteArrayList<String> list =
new CopyOnWriteArrayList<String>();

也就把容器list換成了 CopyOnWriteArrayList,其他的沒變。執行緒裡面的list不用改。因為 CopyOnWriteArrayList實現的也是list<E> 介面。看結果:

其結果沒報錯。
CopyOnWriteArrayList add(E
) 和remove(int index)都是對新的陣列進行修改和新增。所以在多執行緒操作時不會出現java.util.ConcurrentModificationException錯誤。
所以最後得出結論:CopyOnWriteArrayList適合使用在讀操作遠遠大於寫操作的場景裡,比如快取。發生修改時候做copy,新老版本分離,保證讀的高效能,適用於以讀為主的情況。


相關文章