http://blog.csdn.net/zljjava/article/details/48139465
列表實現有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;
- }
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的包裝類,代碼如下:
其中,SynchronizedList對部分操作加上了synchronized關鍵字以保證線程安全。但其iterator()操作還不是線程安全的。部分SynchronizedList的代碼如下:
- public static <T> List<T> synchronizedList(List<T> list) {
- return (list instanceof RandomAccess ?
- new SynchronizedRandomAccessList<T>(list) :
- new SynchronizedList<T>(list));//根據不同的list類型最終實現不同的包裝類。
- }
- 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>
- }
從字面可以知道,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]);
- }
其中setArray()操作僅僅是對array進行引用賦值。Java中“=”操作只是將引用和某個對象關聯,假如同時有一個線程將引用指向另外一個對象,一個線程獲取這個引用指向的對象,那么他們之間不會發生ConcurrentModificationException,他們是在虛擬機層面阻塞的,而且速度非常快,是一個原子操作,幾乎不需要CPU時間。
在列表有更新時直接將原有的列表復制一份,並再新的列表上進行更新操作,完成后再將引用移到新的列表上。舊列表如果仍在使用中(比如遍歷)則繼續有效。如此一來就不會出現修改了正在使用的對象的情況(讀和寫分別發生在兩個對象上),同時讀操作也不必等待寫操作的完成,免去了鎖的使用加快了讀取速度。
測試代碼:
- 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 |
讀操作:在多線程進行讀時,Collections.synchronizedList和CopyOnWriteArrayList均有性能的降低,但是Collections.synchronizedList的性能降低更加顯著。
4 結論
CopyOnWriteArrayList,發生修改時候做copy,新老版本分離,保證讀的高性能,適用於以讀為主,讀操作遠遠大於寫操作的場景中使用,比如緩存。而Collections.synchronizedList則可以用在CopyOnWriteArrayList不適用,但是有需要同步列表的地方,
讀寫操作都比較均勻的地方。
