1. 程式人生 > >List中的併發與同步類

List中的併發與同步類

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

1 ArrayList

        ArrayList是非線性安全,此類的 iterator 和 listIterator 方法返回的迭代器是快速失敗的:在建立迭代器之後,除非通過迭代器自身的 remove 或 add 方法從結構上對列表進行修改,否則在任何時間以任何方式對列表進行修改,迭代器都會丟擲 ConcurrentModificationException。即在一方在便利列表,而另一方在修改列表時,會報ConcurrentModificationException錯誤。而這不是唯一的併發時容易發生的錯誤,在多執行緒進行插入操作時,由於沒有進行同步操作,容易丟失資料。

 

public boolean add(E e) {  
ensureCapacity(size + 1);  // Increments modCount!!  
elementData[size++] = e;//使用了size++操作,會產生多執行緒資料丟失問題。  
return true;  
   }  

        因此,在開發過程當中,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的包裝類,程式碼如下:

public static <T> List<T> synchronizedList(List<T> list) {  
return (list instanceof RandomAccess ?  
               new SynchronizedRandomAccessList<T>(list) :  
               new SynchronizedList<T>(list));//根據不同的list型別最終實現不同的包裝類。  
   }  

其中,SynchronizedList對部分操作加上了synchronized關鍵字以保證執行緒安全。但其iterator()操作還不是執行緒安全的。部分SynchronizedList的程式碼如下:

public E get(int index) {  
        synchronized(mutex) {return list.get(index);}  
        }  
    public E set(int index, E element) {  
        synchronized(mutex) {return list.set(index, element);}  
        }  
    public void add(int index, E element) {  
        synchronized(mutex) {list.add(index, element);}  
        }  
    public ListIterator<E> listIterator() {  
        return list.listIterator(); // Must be manually synched by user 需要使用者保證同步,否則仍然可能丟擲ConcurrentModificationException  
        }  
  
    public ListIterator<E> listIterator(int index) {  
        return list.listIterator(index); // Must be manually synched by user <span style="font-family: Arial, Helvetica, sans-serif;">需要使用者保證同步,否則仍然可能丟擲ConcurrentModificationException</span>  
        }  

3.2 CopyOnWriteArrayList

        從字面可以知道,CopyOnWriteArrayList線上程對其進行些操作的時候,會拷貝一個新的陣列以存放新的欄位。其寫操作的程式碼如下:

/** The lock protecting all mutators */  
    transient final ReentrantLock lock = new ReentrantLock();  
  
    /** The array, accessed only via getArray/setArray. */  
    private volatile transient Object[] array;//保證了執行緒的可見性  
      
     public boolean add(E e) {  
    final ReentrantLock lock = this.lock;//ReentrantLock 保證了執行緒的可見性和順序性,即保證了多執行緒安全。  
    lock.lock();  
    try {  
        Object[] elements = getArray();  
        int len = elements.length;  
        Object[] newElements = Arrays.copyOf(elements, len + 1);//在原先陣列基礎之上新建長度+1的陣列,並將原先陣列當中的內容拷貝到新陣列當中。  
        newElements[len] = e;//設值  
        setArray(newElements);//對新陣列進行賦值  
        return true;  
    } finally {  
        lock.unlock();  
    }  
    }  

        其讀操作程式碼如下:

public E get(int index) {  
       return (E)(getArray()[index]);  
   }  

        其沒有加任何同步關鍵字,根據以上寫操作的程式碼可知,其每次寫操作都會進行一次陣列複製操作,然後對新複製的陣列進行些操作,不可能存在在同時又讀寫操作在同一個陣列上(不是同一個物件),而讀操作並沒有對陣列修改,不會產生執行緒安全問題。Java中兩個不同的引用指向同一個物件,當第一個引用指向另外一個物件時,第二個引用還將保持原來的物件。
        其中setArray()操作僅僅是對array進行引用賦值。Java中“=”操作只是將引用和某個物件關聯,假如同時有一個執行緒將引用指向另外一個物件,一個執行緒獲取這個引用指向的物件,那麼他們之間不會發生ConcurrentModificationException,他們是在虛擬機器層面阻塞的,而且速度非常快,是一個原子操作,幾乎不需要CPU時間。

 

        在列表有更新時直接將原有的列表複製一份,並再新的列表上進行更新操作,完成後再將引用移到新的列表上。舊列表如果仍在使用中(比如遍歷)則繼續有效。如此一來就不會出現修改了正在使用的物件的情況(讀和寫分別發生在兩個物件上),同時讀操作也不必等待寫操作的完成,免去了鎖的使用加快了讀取速度。

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

        測試程式碼:

package com.yang.test;  
  
import org.junit.Test;  
  
import java.util.*;  
import java.util.concurrent.*;  
  
/** 
 * Created with IntelliJ IDEA. 
 * User: yangzl2008 
 * Date: 14-9-18 
 * Time: 下午8:36 
 * To change this template use File | Settings | File Templates. 
 */  
public class Test02 {  
  
    private int NUM = 10000;  
    private int THREAD_COUNT = 16;  
  
    @Test  
    public void testAdd() throws Exception {  
        List<Integer> list1 = new CopyOnWriteArrayList<Integer>();  
        List<Integer> list2 = Collections.synchronizedList(new ArrayList<Integer>());  
        Vector<Integer> v  = new Vector<Integer>();  
  
        CountDownLatch add_countDownLatch = new CountDownLatch(THREAD_COUNT);  
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);  
  
        int add_copyCostTime = 0;  
        int add_synchCostTime = 0;  
        for (int i = 0; i < THREAD_COUNT; i++) {  
            add_copyCostTime += executor.submit(new AddTestTask(list1, add_countDownLatch)).get();  
        }  
        System.out.println("CopyOnWriteArrayList add method cost time is " + add_copyCostTime);  
  
        for (int i = 0; i < THREAD_COUNT; i++) {  
            add_synchCostTime += executor.submit(new AddTestTask(list2, add_countDownLatch)).get();  
        }  
        System.out.println("Collections.synchronizedList add method cost time is " + add_synchCostTime);  
  
  
    }  
  
    @Test  
    public void testGet() throws Exception {  
        List<Integer> list = initList();  
  
        List<Integer> list1 = new CopyOnWriteArrayList<Integer>(list);  
        List<Integer> list2 = Collections.synchronizedList(list);  
  
        int get_copyCostTime = 0;  
        int get_synchCostTime = 0;  
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);  
        CountDownLatch get_countDownLatch = new CountDownLatch(THREAD_COUNT);  
        for (int i = 0; i < THREAD_COUNT; i++) {  
            get_copyCostTime += executor.submit(new GetTestTask(list1, get_countDownLatch)).get();  
        }  
        System.out.println("CopyOnWriteArrayList add method cost time is " + get_copyCostTime);  
  
        for (int i = 0; i < THREAD_COUNT; i++) {  
            get_synchCostTime += executor.submit(new GetTestTask(list2, get_countDownLatch)).get();  
        }  
        System.out.println("Collections.synchronizedList add method cost time is " + get_synchCostTime);  
  
    }  
  
  
    private List<Integer> initList() {  
        List<Integer> list = new ArrayList<Integer>();  
        int num = new Random().nextInt(1000);  
        for (int i = 0; i < NUM; i++) {  
            list.add(num);  
        }  
        return list;  
    }  
  
    class AddTestTask implements Callable<Integer> {  
        List<Integer> list;  
        CountDownLatch countDownLatch;  
  
        AddTestTask(List<Integer> list, CountDownLatch countDownLatch) {  
            this.list = list;  
            this.countDownLatch = countDownLatch;  
        }  
  
        @Override  
        public Integer call() throws Exception {  
            int num = new Random().nextInt(1000);  
            long start = System.currentTimeMillis();  
            for (int i = 0; i < NUM; i++) {  
                list.add(num);  
            }  
            long end = System.currentTimeMillis();  
            countDownLatch.countDown();  
            return (int) (end - start);  
        }  
    }  
  
    class GetTestTask implements Callable<Integer> {  
        List<Integer> list;  
        CountDownLatch countDownLatch;  
  
        GetTestTask(List<Integer> list, CountDownLatch countDownLatch) {  
            this.list = list;  
            this.countDownLatch = countDownLatch;  
        }  
  
        @Override  
        public Integer call() throws Exception {  
            int pos = new Random().nextInt(NUM);  
            long start = System.currentTimeMillis();  
            for (int i = 0; i < NUM; i++) {  
                list.get(pos);  
            }  
            long end = System.currentTimeMillis();  
            countDownLatch.countDown();  
            return (int) (end - start);  
        }  
    }  
}  

操作結果:

  寫操作 讀操作
  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不適用,但是有需要同步列表的地方,讀寫操作都比較均勻的地方。