Java同步容器與併發容器
阿新 • • 發佈:2019-02-08
Java容器
Java提供了很多容器類,方便使用者使用。關鍵介面圖如下(圖片來源自The Java™ Tutorials),
- Collection——集合框架結構的根節點。Java並沒有提供該介面的具體實現,但是提供了Collection子介面,比如Set,List的具體實現。
- Set——沒有重複元素的集合。
- List——有序的集合(或稱為序列)。可以包括重複元素。
- Queue——用於儲存多個待處理的元素。典型地(但非必須),滿足FIFO(先進先出)規則。
- Deque——儲存多個待處理的元素。支援先進先出(FIFO)和先進後出(LIFO)。雙向,元素可以在兩個方向新增或刪除。
- Map——將鍵(key)對映到值(value)的結構。不能包含相同的key,每個key至多對映到一個value。
同步容器類
我們平時用的ArrayList,Queue,HashMap,都不是同步安全的。如果我們在多個執行緒中這些類的例項,就可能發生執行緒干擾或記憶體一致性錯誤。
我們可以使用同步訪問來解決這個問題,但是Java已經幫我們實現了,這就是接下來的同步容器類。同步容器類包括:
- Vector和Hashtable,還有繼承Vector的Stack類。
- Collections.synchronizedXxx等工廠方法建立的類。
這些類實現執行緒安全的方法是:將它們的成員變數設定為私有(狀態封裝),並對每個公有方法都進行同步,使得每次只有一個執行緒能訪問類的例項。
我們可以方便得使用這些同步容器來實現多個執行緒同步訪問。但是並不意味同步容器可以完全保證執行緒安全。考慮以下的例子,
public static Object getLast(Vector vector) {
int lastIndex = vector.size() - 1;
return vector.get(lastIndex);
}
public static void deleteLast(Vector vector) {
int lastIndex = vector.size() - 1;
vector.remove(lastIndex);
}
這兩個程式碼都會執行“先檢查再執行”操作。先獲得向量的大小,然後根據結果來得到或刪除最後一個元素。如果這兩個程式碼執行在兩個執行緒中,會發生以下的執行序列,
- getLast得到向量大小,為10,
- deleteLast得到向量大小,為10,
- deleteLast刪除最後一個元素,此時向量的大小為9
- getLast得到最後一個元素,get(9),但是由於deleteLast將最後一個元素刪除,此時長度為9,所以此時會出錯。
容器上常見的複合操作包括:迭代、跳轉以及條件運算,例如“沒有則新增”。在同步容器中,這些複合操作是執行緒安全的,但是在其他執行緒併發地修改容器時,就會發生意料之外的行為。比如上面的例子。
我們可以通過客戶端加鎖來解決該問題,使得getLast和deleteLast成為原子操作,確保vector的大小在呼叫size和get之間不會發生變化,示例程式碼如下:
public static Object getLast(Vector vector) {
synchronized (vector) {
int lastIndex = vector.size() - 1;
return vector.get(lastIndex);
}
}
public static void deleteLast(Vector vector) {
synchronized (vector) {
int lastIndex = vector.size() - 1;
vector.remove(lastIndex);
}
}
併發容器類
同步容器將所有對容器狀態的訪問都序列化,來實現執行緒安全性。這種方法的代價是嚴重降低併發性,當多個執行緒競爭容器的鎖時,吞吐量將嚴重降低。
Java 提供了多種併發容器來改善同步容器的效能。另外,併發容器是針對多個執行緒併發訪問設計的。
- 替代Map的ConcurrentHashMap
- 替代SortedMap的ConcurrentSkipListMap
- 替代SortedSet的ConcurrentSkipListSet
- 替代List的CopyOnWriteArrayList
- ConcurrentLinkedQueue,先進先出佇列
- BlockingQueue,貨站了Queue,支援可阻塞的插入和獲取等操作