淺析Collections.synchronizedList實現原理及如何做到線程安全、實現線程安全2種方式CopyOnWriteArrayList與Collections.synchronizedList的讀寫性能對比


一、Collections.synchronizedList 實現原理及如何做到線程安全

  大家都知道ArrayList並不是線程安全的,如果想要做到線程安全,我們可以使用 Collections.synchronizedList, 但是使用 Collections.synchronizedList后是否真的就線程安全了?

1、Collections.synchronizedList 原理

  我們先來看看Collections.synchronizedList 做了什么。

    從源碼來看,SynchronizedList 就是在 List的操作外包加了一層 synchronize 同步控制。

2、加了 Collections.synchronizedList 后,為什么還需要使用 synchronized ?

首先我們看官方文檔,可以發現, 當用戶通過迭代器遍歷返回列表時,必須手動同步:

It is imperative that the user manually synchronize on the returned list when traversing it via [Iterator]

List list = Collections.synchronizedList(new ArrayList()); ... synchronized (list) { Iterator i = list.iterator(); // Must be in synchronized block
      while (i.hasNext()) foo(i.next()); }

  也就是說官方文檔明確提出:對於 使用 Iterator 遍歷列表時,Collections.synchronizedList 可能發生錯誤。

  那除了直接使用 Iterator 要加 synchronize 保證線程安全,還有什么情況會間接使用到 Iterator嗎? 那就是 for each增強for循環;

  在使用 Iteratior 遍歷的同時,異步修改List的結構,發現拋出了 ConcurrentModificationException 異常;

  那怎么解決呢?官方文檔說的很清楚,我們在迭代器遍歷返回列表時,增加手動同步處理,下面是IteratorRunnable 修改后 代碼,僅僅是在外層加了 synchronized.

    static class IteratorRunnable implements Runnable { private List<Integer> list; public IteratorRunnable(List<Integer> synchronizeList) { this.list = synchronizeList; } @Override public void run() { while(true) { synchronized (list) { for (Integer i : list) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(i + ","); } } } } }

  從運行結果來看,增加了synchronized 后,不會出現ConcurrentModificationException異常了。

3、探究下for each Java的實現

  我們先來看看.class文件中for each

  看到這,我們就可以確定 ,其實JAVA中的增強for循環底層是通過iterator來實現的。

二、CopyOnWriteArrayList與Collections.synchronizedList的性能對比

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

1、ArrayList

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

  因此,在開發過程當中,ArrayList並不適用於多線程的操作。

2、Vector

  從JDK1.0開始,Vector便存在JDK中,Vector是一個線程安全的列表,采用數組實現。其線程安全的實現方式是對所有操作都加上了synchronized關鍵字,這種方式嚴重影響效率,因此,不再推薦使用Vector了,Stackoverflow當中有這樣的描述: Why is Java Vector class considered obsolete or deprecated?。  為什么 Java Vector(和 Stack)類被認為已過時或已棄用?

3、Collections.synchronizedList & CopyOnWriteArrayList

  CopyOnWriteArrayList 和 Collections.synchronizedList 是實現線程安全的列表的兩種方式。

  兩種實現方式分別針對不同情況有不同的性能表現,其中 CopyOnWriteArrayList的寫操作性能較差,而多線程的讀操作性能較好。

  而Collections.synchronizedList的寫操作性能比CopyOnWriteArrayList在多線程操作的情況下要好很多,而讀操作因為是采用了synchronized關鍵字的方式,其讀操作性能並不如CopyOnWriteArrayList。

  因此在不同的應用場景下,應該選擇不同的多線程安全實現類。

4、Collections.synchronizedList

  Collections.synchronizedList的源碼可知,其實現線程安全的方式是建立了list的包裝類。

  其中,SynchronizedList對部分操作加上了synchronized關鍵字以保證線程安全。但其iterator()操作還不是線程安全的。

5、CopyOnWriteArrayList

  從字面可以知道,CopyOnWriteArrayList在線程對其進行些操作的時候,會拷貝一個新的數組以存放新的字段。

  其沒有加任何同步關鍵字,根據以上寫操作的代碼可知,其每次寫操作都會進行一次數組復制操作,然后對新復制的數組進行些操作,不可能存在在同時又讀寫操作在同一個數組上( 不是同一個對象),而讀操作並沒有對數組修改,不會產生線程安全問題。

  Java中兩個不同的引用指向同一個對象,當第一個引用指向另外一個對象時,第二個引用還將保持原來的對象。

  其中setArray()操作僅僅是對array進行引用賦值。Java中“=”操作只是將引用和某個對象關聯,假如同時有一個線程將引用指向另外一個對象,一個線程獲取這個引用指向的對象,那么他們之間不會發生ConcurrentModificationException,他們是在虛擬機層面阻塞的,而且速度非常快,是一個原子操作,幾乎不需要CPU時間。

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

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

(1)寫操作:在線程數目增加時CopyOnWriteArrayList的寫操作性能下降非常嚴重,而Collections.synchronizedList雖然有性能的降低,但下降並不明顯。

(2)讀操作:在多線程進行讀時,Collections.synchronizedList和CopyOnWriteArrayList均有性能的降低,但是Collections.synchronizedList的性能降低更加顯著。

  結論: 

  CopyOnWriteArrayList,發生修改時候做 copy,新老版本分離,保證讀的高性能,適用於以讀為主,讀操作遠遠大於寫操作的場景中使用,比如緩存。

  而Collections.synchronizedList則可以用在CopyOnWriteArrayList不適用,但是有需要同步列表的地方, 讀寫操作都比較均勻的地方。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM