CopyOnWriteArrayList與Collections.synchronizedList的性能對比(轉)

列表實現有ArrayList、Vector、CopyOnWriteArrayList、Collections.synchronizedList(list)四種方式。

1 ArrayList

        ArrayList是非線性安全,此類的 iterator 和 listIterator 方法返回的迭代器是快速失敗的:在建立迭代器以後,除非經過迭代器自身的 remove 或 add 方法從結構上對列表進行修改,不然在任什麼時候間以任何方式對列表進行修改,迭代器都會拋出 ConcurrentModificationException。即在一方在便利列表,而另外一方在修改列表時,會報ConcurrentModificationException錯誤。而這不是惟一的併發時容易發生的錯誤,在多線程進行插入操做時,因爲沒有進行同步操做,容易丟失數據。java

 

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
 
  1. public boolean add(E e) {  
  2. ensureCapacity(size + 1);  // Increments modCount!!  
  3. elementData[size++] = e;//使用了size++操做,會產生多線程數據丟失問題。  
  4. return true;  
  5.    }  
        所以,在開發過程中,ArrayList並不適用於多線程的操做。

 

 

2 Vector

        從JDK1.0開始,Vector便存在JDK中,Vector是一個線程安全的列表,採用數組實現。其線程安全的實現方式是對全部操做都加上了synchronized關鍵字,這種方式嚴重影響效率,所以,再也不推薦使用Vector了,Stackoverflow當中有這樣的描述: Why is Java Vector class considered obsolete or deprecated?

3 Collections.synchronizedList & CopyOnWriteArrayList

       CopyOnWriteArrayList和Collections.synchronizedList是實現線程安全的列表的兩種方式。兩種實現方式分別針對不一樣狀況有不一樣的性能表現,其中CopyOnWriteArrayList的寫操做性能較差,而多線程的讀操做性能較好。而Collections.synchronizedList的寫操做性能比CopyOnWriteArrayList在多線程操做的狀況下要好不少,而讀操做由於是採用了synchronized關鍵字的方式,其讀操做性能並不如CopyOnWriteArrayList。所以在不一樣的應用場景下,應該選擇不一樣的多線程安全實現類。

3.1 Collections.synchronizedList

        Collections.synchronizedList的源碼可知,其實現線程安全的方式是創建了list的包裝類,代碼以下:
[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
 
  1. public static <T> List<T> synchronizedList(List<T> list) {  
  2. return (list instanceof RandomAccess ?  
  3.                new SynchronizedRandomAccessList<T>(list) :  
  4.                new SynchronizedList<T>(list));//根據不一樣的list類型最終實現不一樣的包裝類。  
  5.    }  
其中,SynchronizedList對部分操做加上了synchronized關鍵字以保證線程安全。但其iterator()操做還不是線程安全的。部分SynchronizedList的代碼以下:
[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
 
  1. public E get(int index) {  
  2.         synchronized(mutex) {return list.get(index);}  
  3.         }  
  4.     public E set(int index, E element) {  
  5.         synchronized(mutex) {return list.set(index, element);}  
  6.         }  
  7.     public void add(int index, E element) {  
  8.         synchronized(mutex) {list.add(index, element);}  
  9.         }  
  10.     public ListIterator<E> listIterator() {  
  11.         return list.listIterator(); // Must be manually synched by user 須要用戶保證同步,不然仍然可能拋出ConcurrentModificationException  
  12.         }  
  13.   
  14.     public ListIterator<E> listIterator(int index) {  
  15.         return list.listIterator(index); // Must be manually synched by user <span style="font-family: Arial, Helvetica, sans-serif;">須要用戶保證同步,不然仍然可能拋出ConcurrentModificationException</span>  
  16.         }  

3.2 CopyOnWriteArrayList

        從字面能夠知道,CopyOnWriteArrayList在線程對其進行些操做的時候,會拷貝一個新的數組以存放新的字段。其寫操做的代碼以下:
[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
 
  1. /** The lock protecting all mutators */  
  2.     transient final ReentrantLock lock = new ReentrantLock();  
  3.   
  4.     /** The array, accessed only via getArray/setArray. */  
  5.     private volatile transient Object[] array;//保證了線程的可見性  
  6.       
  7.      public boolean add(E e) {  
  8.     final ReentrantLock lock = this.lock;//ReentrantLock 保證了線程的可見性和順序性,即保證了多線程安全。  
  9.     lock.lock();  
  10.     try {  
  11.         Object[] elements = getArray();  
  12.         int len = elements.length;  
  13.         Object[] newElements = Arrays.copyOf(elements, len + 1);//在原先數組基礎之上新建長度+1的數組,並將原先數組當中的內容拷貝到新數組當中。  
  14.         newElements[len] = e;//設值  
  15.         setArray(newElements);//對新數組進行賦值  
  16.         return true;  
  17.     } finally {  
  18.         lock.unlock();  
  19.     }  
  20.     }  
        其讀操做代碼以下:
[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
 
  1. public E get(int index) {  
  2.        return (E)(getArray()[index]);  
  3.    }  
        其沒有加任何同步關鍵字,根據以上寫操做的代碼可知,其每次寫操做都會進行一次數組複製操做,而後對新複製的數組進行些操做,不可能存在在同時又讀寫操做在同一個數組上(不是同一個對象),而讀操做並無對數組修改,不會產生線程安全問題。Java中兩個不一樣的引用指向同一個對象,當第一個引用指向另一個對象時,第二個引用還將保持原來的對象。
        其中setArray()操做僅僅是對array進行引用賦值。Java中「=」操做只是將引用和某個對象關聯,假如同時有一個線程將引用指向另一個對象,一個線程獲取這個引用指向的對象,那麼他們之間不會發生ConcurrentModificationException,他們是在虛擬機層面阻塞的,並且速度很是快,是一個原子操做,幾乎不須要CPU時間。
 
        在列表有更新時直接將原有的列表複製一份,並再新的列表上進行更新操做,完成後再將引用移到新的列表上。舊列表若是仍在使用中(好比遍歷)則繼續有效。如此一來就不會出現修改了正在使用的對象的狀況(讀和寫分別發生在兩個對象上),同時讀操做也沒必要等待寫操做的完成,免去了鎖的使用加快了讀取速度。

3.3 Collections.synchronizedList & CopyOnWriteArrayList在讀寫操做上的差距

        測試代碼:
[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
 
 
  1. package com.yang.test;  
  2.   
  3. import org.junit.Test;  
  4.   
  5. import java.util.*;  
  6. import java.util.concurrent.*;  
  7.   
  8. /** 
  9.  * Created with IntelliJ IDEA. 
  10.  * User: yangzl2008 
  11.  * Date: 14-9-18 
  12.  * Time: 下午8:36 
  13.  * To change this template use File | Settings | File Templates. 
  14.  */  
  15. public class Test02 {  
  16.   
  17.     private int NUM = 10000;  
  18.     private int THREAD_COUNT = 16;  
  19.   
  20.     @Test  
  21.     public void testAdd() throws Exception {  
  22.         List<Integer> list1 = new CopyOnWriteArrayList<Integer>();  
  23.         List<Integer> list2 = Collections.synchronizedList(new ArrayList<Integer>());  
  24.         Vector<Integer> v  = new Vector<Integer>();  
  25.   
  26.         CountDownLatch add_countDownLatch = new CountDownLatch(THREAD_COUNT);  
  27.         ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);  
  28.   
  29.         int add_copyCostTime = 0;  
  30.         int add_synchCostTime = 0;  
  31.         for (int i = 0; i < THREAD_COUNT; i++) {  
  32.             add_copyCostTime += executor.submit(new AddTestTask(list1, add_countDownLatch)).get();  
  33.         }  
  34.         System.out.println("CopyOnWriteArrayList add method cost time is " + add_copyCostTime);  
  35.   
  36.         for (int i = 0; i < THREAD_COUNT; i++) {  
  37.             add_synchCostTime += executor.submit(new AddTestTask(list2, add_countDownLatch)).get();  
  38.         }  
  39.         System.out.println("Collections.synchronizedList add method cost time is " + add_synchCostTime);  
  40.   
  41.   
  42.     }  
  43.   
  44.     @Test  
  45.     public void testGet() throws Exception {  
  46.         List<Integer> list = initList();  
  47.   
  48.         List<Integer> list1 = new CopyOnWriteArrayList<Integer>(list);  
  49.         List<Integer> list2 = Collections.synchronizedList(list);  
  50.   
  51.         int get_copyCostTime = 0;  
  52.         int get_synchCostTime = 0;  
  53.         ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);  
  54.         CountDownLatch get_countDownLatch = new CountDownLatch(THREAD_COUNT);  
  55.         for (int i = 0; i < THREAD_COUNT; i++) {  
  56.             get_copyCostTime += executor.submit(new GetTestTask(list1, get_countDownLatch)).get();  
  57.         }  
  58.         System.out.println("CopyOnWriteArrayList add method cost time is " + get_copyCostTime);  
  59.   
  60.         for (int i = 0; i < THREAD_COUNT; i++) {  
  61.             get_synchCostTime += executor.submit(new GetTestTask(list2, get_countDownLatch)).get();  
  62.         }  
  63.         System.out.println("Collections.synchronizedList add method cost time is " + get_synchCostTime);  
  64.   
  65.     }  
  66.   
  67.   
  68.     private List<Integer> initList() {  
  69.         List<Integer> list = new ArrayList<Integer>();  
  70.         int num = new Random().nextInt(1000);  
  71.         for (int i = 0; i < NUM; i++) {  
  72.             list.add(num);  
  73.         }  
  74.         return list;  
  75.     }  
  76.   
  77.     class AddTestTask implements Callable<Integer> {  
  78.         List<Integer> list;  
  79.         CountDownLatch countDownLatch;  
  80.   
  81.         AddTestTask(List<Integer> list, CountDownLatch countDownLatch) {  
  82.             this.list = list;  
  83.             this.countDownLatch = countDownLatch;  
  84.         }  
  85.   
  86.         @Override  
  87.         public Integer call() throws Exception {  
  88.             int num = new Random().nextInt(1000);  
  89.             long start = System.currentTimeMillis();  
  90.             for (int i = 0; i < NUM; i++) {  
  91.                 list.add(num);  
  92.             }  
  93.             long end = System.currentTimeMillis();  
  94.             countDownLatch.countDown();  
  95.             return (int) (end - start);  
  96.         }  
  97.     }  
  98.   
  99.     class GetTestTask implements Callable<Integer> {  
  100.         List<Integer> list;  
  101.         CountDownLatch countDownLatch;  
  102.   
  103.         GetTestTask(List<Integer> list, CountDownLatch countDownLatch) {  
  104.             this.list = list;  
  105.             this.countDownLatch = countDownLatch;  
  106.         }  
  107.   
  108.         @Override  
  109.         public Integer call() throws Exception {  
  110.             int pos = new Random().nextInt(NUM);  
  111.             long start = System.currentTimeMillis();  
  112.             for (int i = 0; i < NUM; i++) {  
  113.                 list.get(pos);  
  114.             }  
  115.             long end = System.currentTimeMillis();  
  116.             countDownLatch.countDown();  
  117.             return (int) (end - start);  
  118.         }  
  119.     }  
  120. }  
操做結果:
  寫操做 讀操做
  CopyOnWriteArrayList  Collections.
synchronizedList
CopyOnWriteArrayList  Collections.
synchronizedList
2 567 2 1 1
4 3088 3 2 2
8 25975 28 2 3
16 295936 44 2 6
32 3 8
64 7 21
128 9 38
        寫操做:在線程數目增長時CopyOnWriteArrayList的寫操做性能降低很是嚴重,而Collections.synchronizedList雖然有性能的下降,但降低並不明顯。
        讀操做:在多線程進行讀時,Collections.synchronizedList和CopyOnWriteArrayList均有性能的下降,可是Collections.synchronizedList的性能下降更加顯著。

4 結論

        CopyOnWriteArrayList,發生修改時候作copy,新老版本分離,保證讀的高性能,適用於以讀爲主,讀操做遠遠大於寫操做的場景中使用,好比緩存。而Collections.synchronizedList則能夠用在CopyOnWriteArrayList不適用,可是有須要同步列表的地方,讀寫操做都比較均勻的地方。
相關文章
相關標籤/搜索