列表實現有ArrayList、Vector、CopyOnWriteArrayList、Collections.synchronizedList(list)四種方式。
1 ArrayList
ArrayList是非線性安全,此類的 iterator 和 listIterator 方法返回的迭代器是快速失敗的:在建立迭代器以後,除非經過迭代器自身的 remove 或 add 方法從結構上對列表進行修改,不然在任什麼時候間以任何方式對列表進行修改,迭代器都會拋出 ConcurrentModificationException。即在一方在便利列表,而另外一方在修改列表時,會報ConcurrentModificationException錯誤。而這不是惟一的併發時容易發生的錯誤,在多線程進行插入操做時,因爲沒有進行同步操做,容易丟失數據。java
- public boolean add(E e) {
- ensureCapacity(size + 1);
- elementData[size++] = e;
- return true;
- }
所以,在開發過程中,ArrayList並不適用於多線程的操做。
2 Vector
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));
- }
其中,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();
- }
-
- public ListIterator<E> listIterator(int index) {
- return list.listIterator(index);
- }
從字面能夠知道,CopyOnWriteArrayList在線程對其進行些操做的時候,會拷貝一個新的數組以存放新的字段。其寫操做的代碼以下:
- transient final ReentrantLock lock = new ReentrantLock();
-
-
- private volatile transient Object[] array;
-
- public boolean add(E e) {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- Object[] elements = getArray();
- int len = elements.length;
- Object[] newElements = Arrays.copyOf(elements, len + 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在讀寫操做上的差距
測試代碼:
|
寫操做 |
讀操做 |
|
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不適用,可是有須要同步列表的地方,讀寫操做都比較均勻的地方。