Java 併發程式設計(九)併發集合框架
集合框架簡介
程式設計中,我們經常需要集中存放多個數據。陣列是我們的一個很好的選擇,前提是我們事先明確我們將要儲存物件的數量。陣列在初始化時如果指定了長度,那這個陣列長度就是不可變的了,如果我們需要儲存一個可以動態增長的資料(編譯時無法確定具體的物件數量),所以Java提供了集合框架來實現這個功能。
集合類主要負責儲存資料,因此集合類也被稱為容器類。Java中集合類都位於java.util包下,後來為了處理多執行緒併發安全問題,Java5在java.util.concurrent包下提供了支援多執行緒併發的集合類。
Java容器類的用途是”儲存物件”,並將其劃分為兩個不同的概念:
- Collection
一組”對立”的元素,通常這些元素都服從某種規則
1.1) List必須保持元素特定的順序
1.2) Set不能有重複元素
1.3) Queue保持一個佇列(先進先出)的順序 - Map
一組成對的”鍵值對”物件
Collection和Map的區別在於容器中每個位置儲存的元素個數:
- Collection 每個位置只能儲存一個元素(物件)
- Map儲存的是”鍵值對”,我們可以通過”鍵”找到該鍵對應的”值”
Java集合框架Collection圖示:
Map集合框架圖示:
Java併發集合框架
我們著重說明一下Java併發的集合框架,就是java.util.concurrent包下的集合類
非阻塞佇列
非阻塞佇列就是在佇列中沒有資料時,對此佇列的操作將出現異常或者返回null,無需等待/阻塞的特色。
在JDK的併發包中,常見的非阻塞佇列有以下幾個:
- ConcurrentHashMap
- ConcurrentSkipListMap
- ConcurrentSkipListSet
- ConcurrentLinkedQueue
- ConcurrentLinkedDeque
- CopyOnWriteArrayList
- CopyOnWriteArraySet
ConcurrentHashMap類
這個類是支援併發操作的Map物件,與之對應的不支援併發操作的Map物件是HashMap,Hashtable物件也是Map的一個子類,也是支援併發操作,但是其不支援Iterator併發的刪除操作,如果程式中要對map集合做併發以及Iterator併發編輯/刪除操作,推薦使用ConcurrentHashMap集合類。
HashMap集合不支援併發示例:
package com.collections.hashmapdemo;
import java.util.HashMap;
public class HashMapDemo {
public static HashMap hashMap = new HashMap();
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
hashMap.put("ThreadA" + (i + 1), "ThreadA" + i + 1);
System.out.println("ThreadA" + (i + 1));
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
hashMap.put("ThreadB" + (i + 1), "ThreadB" + i + 1);
System.out.println("ThreadB" + (i + 1));
}
}
});
threadA.start();
threadB.start();
}
}
執行結果
有時候程式可以正常執行完,有時候則程式會造成假死的狀態,如下圖:
所以說HashMap不支援多執行緒併發的操作。
Hashtable集合支援併發
示例:
只需要把HashMap示例中public static HashMap hashMap = new HashMap();修改成public static Hashtable hashtable = new Hashtable();即可。
我們就說說Hashtable不支援Iterator的編輯/刪除操作,示例如下
package com.collections.hashmapdemo;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
public class HashtableNotModifyDemo {
public static Hashtable hashtable = new Hashtable();
static {
for (int i = 0; i < 5; i++) {
hashtable.put("String" + (i + 1), i + 1);
}
}
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
Iterator iterator = hashtable.keySet().iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
TimeUnit.SECONDS.sleep(2);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
hashtable.put("z", "zValue");
}
});
threadA.start();
threadB.start();
}
}
執行結果
String5
java.util.ConcurrentModificationException
at java.util.HashtableEnumerator.next(Hashtable.java:1167)atcom.collections.hashmapdemo.HashtableNotModifyDemo 1.run(HashtableNotModifyDemo.java:22)
at java.lang.Thread.run(Thread.java:745)
說明多執行緒呼叫該類的iterator()方法返回Iterator物件後,在呼叫put方法會報ConcurrentModificationException異常,也就是不支援Iterator併發的編輯/刪除操作。如果想實現此功能推薦使用併發集合框架提供的ConcurrentHashMap類。
ConcurrentHashMap集合支援併發示例:
需要把HashMap示例中public static HashMap hashMap = new HashMap();修改成public
static ConcurrentHashMap concurretHashMap = new ConcurrentHashMap();即可。
ConcurrentHashMap集合支援Iterator併發操作示例:
把類HashtableNotModifyDemo中的public static Hashtable hashtable = new Hashtable();修改為public static ConcurrentHashMap concurretHashMap = new ConcurrentHashMap();測試即可。
ConcurrentSkipListMap類
這個併發集合類支援排序功能。一般此集合中存放的物件要實現Comparable介面,方便此集合類排序,如果不實現此介面,則會按照預設排序。
ConcurrentSkipListSet類
這個併發集合類不僅支援排序功能,還不允許重複的元素。一般此集合中存放的物件要實現Comparable介面,並且重寫equals和hashCode方法。
ConcurrentLinkedQueue類
這個併發集合類提供了併發環境的佇列操作。
- poll()當沒有獲取到資料時返回null,如果有資料則一處表頭並將表頭資料返回。
- element()當沒有獲取到資料時拋NoSuchElementException異常,如果有資料則返回表頭資料。
- peek()當沒有獲取導資料是返回null,獲取到資料時則不移除表頭,並將表頭資料返回。
ConcurrentLinkedDeque類
ConcurrentLinkedQueue支援對佇列頭操作,而ConcurrentLinkedDeque也支援佇列頭和列尾雙向操作。
CopyOnWriteArrayList類
ArrayList是執行緒不安全的,如果想在併發中實現執行緒安全,則可以使用CopyOnWriteArrayList類,使用方法和ArrayList無異。
CopyOnWriteArraySet類
ArraySet是執行緒不安全的,如果想在併發中實現執行緒安全,則可以使用CopyOnWriteArraySet類,使用方法和ArraySet無異。
阻塞佇列
to be continue……