java並發容器(Map、List、BlockingQueue)具體解釋
Java庫本身就有多種線程安全的容器和同步工具,當中同步容器包含兩部分:一個是Vector和Hashtable。另外還有JDK1.2中增加的同步包裝類。這些類都是由Collections.synchronizedXXX工廠方法。
同步容器都是線程安全的,可是對於復合操作。缺有些缺點:
① 叠代:在查覺到容器在叠代開始以後被改動,會拋出一個未檢查異常ConcurrentModificationEx
② 隱藏叠代器:StringBuilder的toString方法會通過叠代容器中的每一個元素,另外容器的
類似地,
contailAll、removeAll、retainAll方法。以及容器作為參數的構造函數,都會對容器進行叠代。③ 缺少即增加等一些復合操作
public
int
return
}
public
int
list.remove(lastIndex);
}
getLast和deleteLast都是復合操作。由先前對原子性的分析能夠推斷,這依舊存在線程安全問題。有可能會拋出ArrayIndexOutOfBoundsExc
解決的方法就是通過對這些復合操作加鎖
1 並發容器類
正是因為同步容器類有以上問題,導致這些類成了雞肋,於是Java
1.1 ConcurrentHashMap
·
·
·
·
·
putIfAbsent(K
類似於以下的操作
If(!map.containsKey(key)){
return
}else{
return
}
remove(Object
類似於以下的:
if(map.containsKey(key)
Map.remove();
return
}else{
return
}
replace(K
replace(K
上面提到的三個。都是原子的。在一些緩存應用中能夠考慮取代HashMap/Hashtable。
1.2 CopyOnWriteArrayList和CopyOnWriteArraySet
·
應用它們的場合通常在數組相對較小。而且遍歷操作的數量大大超過可變操作的數量時。這樣的場合應用它們很好。它們全部可變的操作都是先取得後臺數組的副本,對副本進行更改。然後替換副本。這樣能夠保證永遠不會拋出ConcurrentModificationEx
2 隊列
Java中的隊列接口就是Queue。它有會拋出異常的add、remove方法。在隊尾插入元素以及對頭移除元素。還有不會拋出異常的,相應的offer、poll方法。
2.1 LinkedList
List實現了deque接口以及List接口,能夠將它看做是這兩種的不論什麽一種。
Queue
queue.offer("testone");
queue.offer("testtwo");
queue.offer("testthree");
queue.offer("testfour");
System.out.println(queue.poll());//testone
2.2 PriorityQueue
一個基於優先級堆(簡單的使用鏈表的話。可能插入的效率會比較低O(N))的無界優先級隊列。
優先級隊列的元素依照其自然順序進行排序,或者依據構造隊列時提供的
優先級隊列不同意使用
queue=new
queue.offer("testone");
queue.offer("testtwo");
queue.offer("testthree");
queue.offer("testfour");
System.out.println(queue.poll());//testfour
2.3 ConcurrentLinkedQueue
基於鏈節點的。線程安全的隊列。
並發訪問不須要同步。在隊列的尾部加入元素,並在頭部刪除他們。
所以僅僅要不須要知道隊列的大小。並發隊列就是比較好的選擇。
3 堵塞隊列
3.1 生產者和消費者模式
生產者和消費者模式,生產者不須要知道消費者的身份或者數量,甚至根本沒有消費者。他們僅僅負責把數據放入隊列。
類似地。消費者也不須要知道生產者是誰,以及是誰給他們安排的工作。
而Java知道大家清楚這個模式的並發復雜性,於是乎提供了堵塞隊列(BlockingQueue)來滿足這個模式的需求。堵塞隊列說起來非常easy,就是當隊滿的時候寫線程會等待,直到隊列不滿的時候;當隊空的時候讀線程會等待。直到隊不空的時候。實現這樣的模式的方法非常多,其差別也就在於誰的消耗更低和等待的策略更優。以LinkedBlockingQueue的詳細實現為例,它的put源代碼例如以下:
public
//
}
撇開其鎖的詳細實現,其流程就是我們在操作系統課上學習到的標準生產者模式,看來那些枯燥的理論還是實用武之地的。當中,最核心的還是Java的鎖實現,有興趣的朋友能夠再進一步深究一下。
堵塞隊列Blocking
3.2 ArrayBlockingQueue和LinkedBlockingQueue
FIFO的隊列。與LinkedList(由鏈節點支持。無界)和ArrayList(由數組支持。有界)相似(Linked有更好的插入和移除性能。Array有更好的查找性能,考慮到堵塞隊列的特性,移除頭部,增加尾部,兩個都差別不大)。可是卻擁有比同步List更好的並發性能。
另外,LinkedList永遠不會等待,由於他是無界的。
BlockingQueue<String>
Producer
Consumer
Consumer
new
new
new
class
class
輸出:
produceK
cousumeK
produceV
cousumeV
produceQ
cousumeQ
produceI
produceD
produceI
produceG
produceA
produceE
cousumeD
3.3 PriorityBlockingQueue
一個按優先級堆支持的無界優先級隊列隊列,假設不希望依照FIFO的順序進行處理,它很實用。
它能夠比較元素本身的自然順序。也能夠使用一個Comparator排序。
3.4 DelayQueue
一個優先級堆支持的。基於時間的調度隊列。加入到隊列中的元素必須實現新的Delayed接口(僅僅有一個方法。Long
static
long
NanoDelay(long
tigger=System.nanoTime()+i;
}
public
return
}
public
long
return
}
public
return
}
public
long
long
if(i<j){
return
}
if(i>j)
return
return
}
}
public
Random
DelayQueue<NanoDelay>
for(int
queue.add(new
}
long
for(int
NanoDelay
long
System.out.println("Trigger
if(i!=0){
System.out.println("Data:
}
last=tt;
}
}
3.5 SynchronousQueue
不是一個真正的隊列,由於它不會為隊列元素維護不論什麽存儲空間,只是它維護一個排隊的線程清單。這些線程等待把元素增加(enqueue)隊列或者移出(dequeue)隊列。
也就是說。它很直接的移交工作,降低了生產者和消費者之間移動數據的延遲時間,另外。也能夠更快的知道反饋信息,當移交被接受時,它就知道消費者已經得到了任務。
由於SynChronousQueue沒有存儲的能力。所以除非還有一個線程已經做好準備,否則put和take會一直阻止。它僅僅有在消費者比較充足的時候比較合適。
4 雙端隊列(Deque)
JAVA6中新增了兩個容器Deque和BlockingDeque,他們分別擴展了Queue和BlockingQueue。Deque它是一個雙端隊列,同意高效的在頭和尾分別進行插入和刪除。它的實現各自是ArrayDeque和LinkedBlockingQueue。
雙端隊列使得他們可以工作在一種稱為“竊取工作”的模式上面。
5 最佳實踐
1..同步的(synchronized)+HashMap,假設不存在,則計算,然後增加,該方法須要同步。
HashMap
public
V
Ii(result==null){
result=c.compute(arg);
Cache.put(result);
}
Return
}
2.用ConcurrentHashMap取代HashMap+同步.。這種在get和set的時候也基本能保證原子性。可是會帶來反復計算的問題.
Map<A,V>
public
V
Ii(result==null){
result=c.compute(arg);
Cache.put(result);
}
Return
}
3.採用FutureTask取代直接存儲值,這樣能夠在一開始創建的時候就將Task增加
Map<A,FutureTask<V>>
public
FutureTask
//檢查再執行的缺陷
Ii(f==null){
Callable<V>
Public
return
}
};
FutureTask
f=ft;
cache.put(arg,ft;
ft.run();
}
Try{
//堵塞,直到完畢
return
}cach(){
}
}
4.上面還有檢查再執行的缺陷,在高並發的情況下啊,兩方都沒發現FutureTask,而且都放入Map(後一個被前一個替代),都開始了計算。
這裏的解決方式在於。當他們都要放入Map的時候。假設能夠有原子方法。那麽已經有了以後,後一個FutureTask就增加,而且啟動。
public
FutureTask
//檢查再執行的缺陷
Ii(f==null){
Callable<V>
Public
return
}
};
FutureTask
f=cache.putIfAbsent(args,ft); //假設已經存在。返回存在的值。否則返回null
if(f==null){f=ft;ft.run();} //曾經不存在。說明應該開始這個計算
else{ft=null;} //取消該計算
}
Try{
//堵塞,直到完畢
return
}cach(){
}
}
5.上面的程序上來看已經完美了。只是可能帶來緩存汙染的可能性。假設一個計算被取消或者失敗,那麽這個鍵以後的值永遠都是失敗了;一種解決方式是,發現取消或者失敗的task,就移除它,假設有Exception,也移除。
6.另外,假設考慮緩存過期的問題,能夠為每一個結果關聯一個過去時間。並周期性的掃描,清除過期的緩存。
(過期時間能夠用Delayed接口實現,參考DelayQueue,給他一個大於當前時間XXX的時間,,而且不斷減去當前時間,直到返回負數,說明延遲時間已到了。
)
List
Map接口是一組成對的鍵-值對象,即所持有的是key-value
Map中不能有反復的key。擁有自己的內部排列機制
Java1.2前的容器:Vector,Stack,Hashtable。
Java1.2的容器:HashSet,TreeSet。HashMap。TreeMap,WeakHashMap
Java1.4的容器:LinkedHashSet,LinkedHashMap,IdentityHashMap,ConcurrentMap。concurrentHashMap
java1.5新增:CopyOnWriteArrayList,AttributeList,RoleList,RoleUnresolvedList,
未知:JobStateReasons
一。
二。
一,
註意1:concurrentHashMap叠代器它們不會拋出ConcurrentModificationEx
只是,叠代器被設計成每次僅由一個線程使用。
二,
list容器:ArrayList,LinkedList,AttributeList,RoleList,RoleUnresolvedList
set容器:HashSet,TreeSet,TreeSet,LinkedHashSet
map容器:HashMap。TreeMap,LinkedHashMap,WeakHashMap,IdentityHashMap
不管怎樣
list容器:PriorityQueue(1.5)。PriorityBlockingQueue
set容器:TreeSet(1.2)
map容器:TreeMap(1.2)
RandomAccess接口是List
在對List特別的遍歷算法中。要盡量來推斷是屬於
由於適合RandomAccess
即對於實現了RandomAccess接口的類實例而言,此循環
的執行速度要快於下面循環:
java並發容器(Map、List、BlockingQueue)具體解釋