并发容器
http://chenzehe.iteye.com/blog/1779990
Java在JDK1.5之前基本上對所有集合都實現了線程同步版本synchronized*,用集合工具類Collections即可得到,如下都為Collections中的方法:
static <T> Collection<T> synchronizedCollection(Collection<T> c) 返回指定 collection 支持的同步(線程安全的)collection。 static <T> List<T> synchronizedList(List<T> list) 返回指定列表支持的同步(線程安全的)列表。 static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) 返回由指定映射支持的同步(線程安全的)映射。 static <T> Set<T> synchronizedSet(Set<T> s) 返回指定 set 支持的同步(線程安全的)set。 static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m) 返回指定有序映射支持的同步(線程安全的)有序映射。 static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s) 返回指定有序 set 支持的同步(線程安全的)有序 set。
其內部實現是在內部維護一個對應的集合類型,然后相應所有的操作都加上同步關鍵字synchronized,同步方法內再調用內部集合的方法,如下為synchronizedMap的實現:
http://xyiyy.iteye.com/blog/361905
不過在使用Iterator遍歷對象時,仍必須實現同步化。因為這樣的List使用iterator()方法返回的Iterator對象,并沒有保證線程安全。一個實現遍歷的例子如下:
List list = Collections.synchronizedList(new ArrayList()); ... synchronized(list) {Iterator i = list.iterator();while(i.hasNext()){foo(i.next());} }
J2SE5.0之后,新增了java.util.concurrent這個包,其中包括一些確保線程安全的Collection類,如ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet等。這些對象與先前介紹的Map、List、Set等對象是相同的,不同的是增加了同步的功能,而且依對象存取時的需求不同而有不同的同步化實現,以同時確保效率和安全性。例如ConcurrentHashMap對Hash Table中不同的區段進行同步化。
使用Collections.synchronizedList/Set/Map可能獲得一個線程安全的容器,但是效率并不是最高
對于Map而言,推薦使用ConcurrentHashMap
ConcurrentlinkedQueue的效率也高于BlockingQueue,BlockingQueue是一個接口,對應有兩個具體的類,ArrayBlockingQueue和LinkedBlockingQueue
LinkedBlockingDeque沒有進行讀寫鎖的分離,同一時間只能有一個線程訪問,因此,效率低于LinkedBlockingQueue
這是因為Concurrent系列都通過冗余空間獲得了近乎無鎖的效率
1. CopyOnWriteArrayList
適用于大多是讀,而寫很少的環境下。
CopyOnWriteArrayList在進行數據修改時,都不會對數據進行鎖定,每次修改時,先拷貝整個數組,然后修改其中的一些元素,完成上述操作后,替換整個數組的指針。 對CopyOnWriteArrayList進行讀取時,也不進行數據鎖定,直接返回需要查詢的數據,如果需要返回整個數組,那么會將整個數組拷貝一份,再返回,保證內部array在任何情況下都是只讀的。
應用場景 正因為上述讀寫特性,如果需要頻繁對CopyOnWriteArrayList進行修改,而很少讀取的話,那么會嚴重降低系統性能。 因為沒有鎖的干預,所以CopyOnWriteArrayLIst在少量修改,頻繁讀取的場景下,有很好的并發性能。
在那些遍歷操作大大地多于插入或移除操作的并發應用程序中,一般用?CopyOnWriteArrayList?類替代?ArrayList?。如果是用于存放一個偵聽器(listener)列表,例如在AWT或Swing應用程序中,或者在常見的JavaBean中,那么這種情況很常見(相關的CopyOnWriteArraySet?使用一個?CopyOnWriteArrayList?來實現?Set?接口) 。
如果您正在使用一個普通的?ArrayList?來存放一個偵聽器列表,那么只要該列表是可變的,而且可能要被多個線程訪問,您 就必須要么在對其進行迭代操作期間,要么在迭代前進行的克隆操作期間,鎖定整個列表,這兩種做法的開銷都很大。當對列表執行會引起列表發生變化的操作時,?CopyOnWriteArrayList?并不是為列表創建一個全新的副本,它的迭代器肯定能夠返回在迭代器被創建時列表的狀態,而不會拋出?ConcurrentModificationException?。在對列表進行迭代之前不必克隆列表或者在迭代期間鎖 定列表,因為迭代器所看到的列表的副本是不變的。換句話說,?CopyOnWriteArrayList?含有對一個不可變數組的一個可變的引用,因此,只要保留好那個引用,您就可以獲得不可變的線程安全性的好處,而且不用鎖 定列表。
package javatest;import java.io.*; import java.io.FileInputStream; import java.lang.reflect.*; import java.util.*; import java.util.concurrent.*;import info.*;public class test {class writer implements Runnable {List<Integer> ls;public writer(List<Integer> ls) {this.ls = ls;}public void run(){for (int i = 6; i < 12; ++i) {ls.add(i);System.out.println("add " + i);try {Thread.sleep(10);}catch(InterruptedException e) {e.printStackTrace();}}}}class del implements Runnable {List<Integer> ls;public del(List<Integer> ls) {this.ls = ls;}public void run() {int i = 0;while (i < 6 && !ls.isEmpty()) {//unsafe++i;int len = ls.size();if (len >= 2) {System.out.println("rm " + ls.get(len - 2));ls.remove(len - 2);//unsafe}try {Thread.sleep(1000);}catch(InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {test t = new test();CopyOnWriteArrayList<Integer> ls = new CopyOnWriteArrayList<Integer>(Arrays.asList(1,2,3,4));new Thread(t.new writer(ls)).start();new Thread(t.new del(ls)).start();Iterator<Integer> it = ls.iterator();while (it.hasNext()) {System.out.println(it.next());//it.remove();try {Thread.sleep(1000);}catch(InterruptedException e) {e.printStackTrace();}}}}
創建迭代器時, 迭代器就初始化了當前數組的快照. 就算迭代期間進行了寫操作, 也不會影響到迭代器中的snapshot數組. 所以CopyOnWriteArrayList返回的迭代器只反應迭代發生時CopyOnWriteArrayList對象所持有的集合, 迭代期間發生的改變不會反應出來.所以在用iterator遍歷的時候不需要額外的同步操作,但是it.remove不是同步操作,如果第66行不被注釋,會產生異常
http://ifeve.com/why-is-there-not-concurrent-arraylist-in-java-util-concurrent-package
問:JDK 5在java.util.concurrent里引入了ConcurrentHashMap,在需要支持高并發的場景,我們可以使用它代替HashMap。但是為什么沒有ArrayList的并發實現呢?難道在多線程場景下我們只有Vector這一種線程安全的數組實現可以選擇么?為什么在java.util.concurrent 沒有一個類可以代替Vector呢?
答:我認為在java.util.concurrent包中沒有加入并發的ArrayList實現的主要原因是:很難去開發一個通用并且沒有并發瓶頸的線程安全的List。
像ConcurrentHashMap這樣的類的真正價值(The real point / value of classes)并不是它們保證了線程安全。而在于它們在保證線程安全的同時不存在并發瓶頸。舉個例子,ConcurrentHashMap采用了鎖分段技術和弱一致性的Map迭代器去規避并發瓶頸。
所以問題在于,像“Array List”這樣的數據結構,你不知道如何去規避并發的瓶頸。拿contains() 這樣一個操作來說,當你進行搜索的時候如何避免鎖住整個list?
另一方面,Queue 和Deque (基于Linked List)有并發的實現是因為他們的接口相比List的接口有更多的限制,這些限制使得實現并發成為可能。
CopyOnWriteArrayList是一個有趣的例子,它規避了只讀操作(如get/contains)并發的瓶頸,但是它為了做到這點,在修改操作中做了很多工作和修改可見性規則。 此外,修改操作還會鎖住整個List,因此這也是一個并發瓶頸。所以從理論上來說,CopyOnWriteArrayList并不算是一個通用的并發List。
與50位技術專家面對面20年技術見證,附贈技術全景圖
總結
- 上一篇: 《JAVA与模式》之模板方法模式
- 下一篇: ConcurrentHashMap之实现