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