并发容器
http://chenzehe.iteye.com/blog/1779990
Java在JDK1.5之前基本上對(duì)所有集合都實(shí)現(xiàn)了線程同步版本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。
其內(nèi)部實(shí)現(xiàn)是在內(nèi)部維護(hù)一個(gè)對(duì)應(yīng)的集合類型,然后相應(yīng)所有的操作都加上同步關(guān)鍵字synchronized,同步方法內(nèi)再調(diào)用內(nèi)部集合的方法,如下為synchronizedMap的實(shí)現(xiàn):
http://xyiyy.iteye.com/blog/361905
不過(guò)在使用Iterator遍歷對(duì)象時(shí),仍必須實(shí)現(xiàn)同步化。因?yàn)檫@樣的List使用iterator()方法返回的Iterator對(duì)象,并沒(méi)有保證線程安全。一個(gè)實(shí)現(xiàn)遍歷的例子如下:
List list = Collections.synchronizedList(new ArrayList()); ... synchronized(list) {Iterator i = list.iterator();while(i.hasNext()){foo(i.next());} }
J2SE5.0之后,新增了java.util.concurrent這個(gè)包,其中包括一些確保線程安全的Collection類,如ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet等。這些對(duì)象與先前介紹的Map、List、Set等對(duì)象是相同的,不同的是增加了同步的功能,而且依對(duì)象存取時(shí)的需求不同而有不同的同步化實(shí)現(xiàn),以同時(shí)確保效率和安全性。例如ConcurrentHashMap對(duì)Hash Table中不同的區(qū)段進(jìn)行同步化。
使用Collections.synchronizedList/Set/Map可能獲得一個(gè)線程安全的容器,但是效率并不是最高
對(duì)于Map而言,推薦使用ConcurrentHashMap
ConcurrentlinkedQueue的效率也高于BlockingQueue,BlockingQueue是一個(gè)接口,對(duì)應(yīng)有兩個(gè)具體的類,ArrayBlockingQueue和LinkedBlockingQueue
LinkedBlockingDeque沒(méi)有進(jìn)行讀寫鎖的分離,同一時(shí)間只能有一個(gè)線程訪問(wèn),因此,效率低于LinkedBlockingQueue
這是因?yàn)镃oncurrent系列都通過(guò)冗余空間獲得了近乎無(wú)鎖的效率
1. CopyOnWriteArrayList
適用于大多是讀,而寫很少的環(huán)境下。
CopyOnWriteArrayList在進(jìn)行數(shù)據(jù)修改時(shí),都不會(huì)對(duì)數(shù)據(jù)進(jìn)行鎖定,每次修改時(shí),先拷貝整個(gè)數(shù)組,然后修改其中的一些元素,完成上述操作后,替換整個(gè)數(shù)組的指針。 對(duì)CopyOnWriteArrayList進(jìn)行讀取時(shí),也不進(jìn)行數(shù)據(jù)鎖定,直接返回需要查詢的數(shù)據(jù),如果需要返回整個(gè)數(shù)組,那么會(huì)將整個(gè)數(shù)組拷貝一份,再返回,保證內(nèi)部array在任何情況下都是只讀的。
應(yīng)用場(chǎng)景 正因?yàn)樯鲜鲎x寫特性,如果需要頻繁對(duì)CopyOnWriteArrayList進(jìn)行修改,而很少讀取的話,那么會(huì)嚴(yán)重降低系統(tǒng)性能。 因?yàn)闆](méi)有鎖的干預(yù),所以CopyOnWriteArrayLIst在少量修改,頻繁讀取的場(chǎng)景下,有很好的并發(fā)性能。
在那些遍歷操作大大地多于插入或移除操作的并發(fā)應(yīng)用程序中,一般用?CopyOnWriteArrayList?類替代?ArrayList?。如果是用于存放一個(gè)偵聽器(listener)列表,例如在AWT或Swing應(yīng)用程序中,或者在常見(jiàn)的JavaBean中,那么這種情況很常見(jiàn)(相關(guān)的CopyOnWriteArraySet?使用一個(gè)?CopyOnWriteArrayList?來(lái)實(shí)現(xiàn)?Set?接口) 。
如果您正在使用一個(gè)普通的?ArrayList?來(lái)存放一個(gè)偵聽器列表,那么只要該列表是可變的,而且可能要被多個(gè)線程訪問(wèn),您 就必須要么在對(duì)其進(jìn)行迭代操作期間,要么在迭代前進(jìn)行的克隆操作期間,鎖定整個(gè)列表,這兩種做法的開銷都很大。當(dāng)對(duì)列表執(zhí)行會(huì)引起列表發(fā)生變化的操作時(shí),?CopyOnWriteArrayList?并不是為列表創(chuàng)建一個(gè)全新的副本,它的迭代器肯定能夠返回在迭代器被創(chuàng)建時(shí)列表的狀態(tài),而不會(huì)拋出?ConcurrentModificationException?。在對(duì)列表進(jìn)行迭代之前不必克隆列表或者在迭代期間鎖 定列表,因?yàn)榈魉吹降牧斜淼母北臼遣蛔兊?。換句話說(shuō),?CopyOnWriteArrayList?含有對(duì)一個(gè)不可變數(shù)組的一個(gè)可變的引用,因此,只要保留好那個(gè)引用,您就可以獲得不可變的線程安全性的好處,而且不用鎖 定列表。
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();}}}}
創(chuàng)建迭代器時(shí), 迭代器就初始化了當(dāng)前數(shù)組的快照. 就算迭代期間進(jìn)行了寫操作, 也不會(huì)影響到迭代器中的snapshot數(shù)組. 所以CopyOnWriteArrayList返回的迭代器只反應(yīng)迭代發(fā)生時(shí)CopyOnWriteArrayList對(duì)象所持有的集合, 迭代期間發(fā)生的改變不會(huì)反應(yīng)出來(lái).所以在用iterator遍歷的時(shí)候不需要額外的同步操作,但是it.remove不是同步操作,如果第66行不被注釋,會(huì)產(chǎn)生異常
http://ifeve.com/why-is-there-not-concurrent-arraylist-in-java-util-concurrent-package
問(wèn):JDK 5在java.util.concurrent里引入了ConcurrentHashMap,在需要支持高并發(fā)的場(chǎng)景,我們可以使用它代替HashMap。但是為什么沒(méi)有ArrayList的并發(fā)實(shí)現(xiàn)呢?難道在多線程場(chǎng)景下我們只有Vector這一種線程安全的數(shù)組實(shí)現(xiàn)可以選擇么?為什么在java.util.concurrent 沒(méi)有一個(gè)類可以代替Vector呢?
答:我認(rèn)為在java.util.concurrent包中沒(méi)有加入并發(fā)的ArrayList實(shí)現(xiàn)的主要原因是:很難去開發(fā)一個(gè)通用并且沒(méi)有并發(fā)瓶頸的線程安全的List。
像ConcurrentHashMap這樣的類的真正價(jià)值(The real point / value of classes)并不是它們保證了線程安全。而在于它們?cè)诒WC線程安全的同時(shí)不存在并發(fā)瓶頸。舉個(gè)例子,ConcurrentHashMap采用了鎖分段技術(shù)和弱一致性的Map迭代器去規(guī)避并發(fā)瓶頸。
所以問(wèn)題在于,像“Array List”這樣的數(shù)據(jù)結(jié)構(gòu),你不知道如何去規(guī)避并發(fā)的瓶頸。拿contains() 這樣一個(gè)操作來(lái)說(shuō),當(dāng)你進(jìn)行搜索的時(shí)候如何避免鎖住整個(gè)list?
另一方面,Queue 和Deque (基于Linked List)有并發(fā)的實(shí)現(xiàn)是因?yàn)樗麄兊慕涌谙啾萀ist的接口有更多的限制,這些限制使得實(shí)現(xiàn)并發(fā)成為可能。
CopyOnWriteArrayList是一個(gè)有趣的例子,它規(guī)避了只讀操作(如get/contains)并發(fā)的瓶頸,但是它為了做到這點(diǎn),在修改操作中做了很多工作和修改可見(jiàn)性規(guī)則。 此外,修改操作還會(huì)鎖住整個(gè)List,因此這也是一個(gè)并發(fā)瓶頸。所以從理論上來(lái)說(shuō),CopyOnWriteArrayList并不算是一個(gè)通用的并發(fā)List。
與50位技術(shù)專家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖
總結(jié)
- 上一篇: 《JAVA与模式》之模板方法模式
- 下一篇: ConcurrentHashMap之实现