Java多執行緒/併發14、保持執行緒間的資料獨立:ConcurrentHashMap應用
在Java 1.5之前,如果需要可以在多執行緒和併發的程式中安全使用的Map,只能在HashTable和Collections.synchronizedMap中選擇,因為它們的put、reomve和containsKey方法都是同步的。我們熟知的HashMap不是執行緒安全的,因此在多執行緒環境下開發不能用這個。
HashTable容器使用synchronized來保證執行緒安全,因此讀和寫都是序列的,線上程競爭激烈的情況 下HashTable的效率非常低下。
HashTable容器在競爭激烈的併發環境下表現出效率低下的原因是所有訪問HashTable的執行緒都必須競爭同一把鎖,那假如容器裡有多把 鎖,每一把鎖用於鎖容器其中一部分資料,那麼當多執行緒訪問容器裡不同資料段的資料時,執行緒間就不會存在鎖競爭,從而可以有效的提高併發訪問效率,這就是 ConcurrentHashMap所使用的鎖分段技術,首先將資料分成一段一段的儲存,然後給每一段資料配一把鎖,當一個執行緒佔用鎖訪問其中一個段資料 的時候,其他段的資料也能被其他執行緒訪問。
簡單的說,Hashtable中採用的鎖機制是一次鎖住整個hash表,從而同一時刻只能由一個執行緒對其進行操作;而ConcurrentHashMap中則是 一次鎖住一個桶(表中的一段)。ConcurrentHashMap預設將hash表分為16個桶,諸如get,put,remove等常用操作只鎖當前需要用到的桶。 這樣,原來只能一個執行緒進入,現在卻能同時有16個寫執行緒執行,併發效能的提升是顯而易見的。
上面說到的16個執行緒指的是寫執行緒,而讀操作大部分時候都不需要用到鎖。只有在size等操作時才需要鎖住整個hash表。
效率提升了,但也存在弊端:
由於一些更新操作,如put(),remove(),putAll(),clear()只鎖住操作的部分,所以在檢索操作不能保證返回的是最新的結果。
在迭代方面,ConcurrentHashMap使用了一種不同的迭代方式。在這種迭代方式中,當iterator被建立後集合再發生改變就不再是丟擲ConcurrentModificationException,取而代之的是在改變時new新的資料從而不影響原有的資料,iterator完成後再將頭指標替換為新的資料,這樣iterator執行緒可以使用原來老的資料,而寫執行緒也可以併發的完成改變。
ConcurrentMap介面定義如下:
public interface ConcurrentMap<K, V> extends Map<K, V> {
V putIfAbsent(K key, V value);
boolean remove(Object key, Object value);
boolean replace(K key, V oldValue, V newValue);
V replace(K key, V value);
}
裡面定義了幾個基於 CAS(Compare and Set)原子操作,使用起來很方便
putifAbsent()方法
很多時候我們希望在元素不存在時插入元素,我們一般會像下面那樣寫程式碼
private final Map<String, Long> map = new Hashmap<>();
public long get(String key) {
if (map.get(key) == null){
return map.put(key, getvalue());
} else{
return map.get(key);
}
}
上面這段程式碼在單執行緒開發中是好用的,但在多執行緒中是有出錯的風險的。這是因為在put操作時並沒有對整個Map加鎖,所以一個執行緒正在put(k,v)的時候,另一個執行緒呼叫get(k)會得到null,這就會造成一個執行緒put的值會被另一個執行緒put的值所覆蓋。當然,我們可以將程式碼封裝到synchronized程式碼塊中,這樣雖然執行緒安全了,但會使你的程式碼變成了單執行緒。
ConcurrentHashMap提供的putIfAbsent(key,value)原子方法的實現了同樣的功能,同時避免了上面的執行緒競爭的風險。
private final Map<String, Long> map = new ConcurrentHashMap<>();
public long get(String key) {
Long val =map.get(key);
if(val == null){
val = getvalue();
Long l = map.putIfAbsent(key, val);
//l != null說明有別的執行緒捷足先登插入了key-value
if (l != null) {
val=l;
}
}
return val;
}
特別注意: putIfAbsent 方法是有返回值的,並且返回值很重要。如果(呼叫該方法時)key-value 已經存在,則返回那個 value 值。如果呼叫時 map 裡沒有找到 key 的 mapping,就插入新的元素並返回一個 null 值。所以,使用 putIfAbsent 方法時切記要對返回值進行判斷。
Replace()方法
舉個例子:統計文字中單詞出現的次數,把單詞出現的次數記錄到一個Map中,程式碼如下:
private final Map<String, Long> wordCounts = new ConcurrentHashMap<>();
public long increase(String word) {
Long oldValue = wordCounts.get(word);
if(oldValue == null) {
wordCounts.put(word, 1L);
}
else{
wordCounts.put(word, oldValue + 1);
}
return newValue;
}
如果多個執行緒併發呼叫這個increase()方法,就會出現問題,因為在wordCounts.put時,其它執行緒已經改寫了wordCounts.put的條件。比如在 當前執行緒執行wordCounts.get(word)之後和wordCounts.put(word, 1L);語句之前,另一個執行緒進行了wordCounts.put操作,當前執行緒再執行下去就會put錯誤的值。
下面用原子操作Replace()方法解決這個問題:
private final ConcurrentMap<String, Long> wordCounts = new ConcurrentHashMap<>();
public long increase(String word) {
Long oldValue, newValue;
while (true) {
oldValue = wordCounts.get(word);
if (oldValue == null) {
// Add the word firstly, initial the value as 1
newValue = 1L;
if (wordCounts.putIfAbsent(word, newValue) == null) {
break;
}
} else {
newValue = oldValue + 1;
if (wordCounts.replace(word, oldValue, newValue)) {
break;
}
}
}
return newValue;
}
用while (true) 是因為原子操作有失敗的可能,因此需要多次嘗試,直到成功。
相關文章
- Java多執行緒/併發13、保持執行緒間的資料獨立: Collections.synchronizedMap應用Java執行緒synchronized
- Java多執行緒/併發05、synchronized應用例項:執行緒間操作共享資料Java執行緒synchronized
- JAVA多執行緒併發Java執行緒
- java多執行緒與併發 - 執行緒池詳解Java執行緒
- Java多執行緒/併發08、中斷執行緒 interrupt()Java執行緒
- java 多執行緒 併發 面試Java執行緒面試
- Java 併發和多執行緒(一) Java併發性和多執行緒介紹[轉]Java執行緒
- 多執行緒併發篇——如何停止執行緒執行緒
- java多執行緒5:執行緒間的通訊Java執行緒
- Java多執行緒——執行緒Java執行緒
- Java多執行緒/併發12、多執行緒訪問static變數Java執行緒變數
- Java高併發與多執行緒(二)-----執行緒的實現方式Java執行緒
- Java多執行緒/併發06、執行緒鎖Lock與ReadWriteLockJava執行緒
- Java併發和多執行緒:序Java執行緒
- Java併發 之 執行緒池系列 (1) 讓多執行緒不再坑爹的執行緒池Java執行緒
- 多執行緒與高併發(二)執行緒安全執行緒
- 併發與多執行緒之執行緒安全篇執行緒
- Java多執行緒/併發07、Thread.Join()讓呼叫執行緒等待子執行緒Java執行緒thread
- 多執行緒程式設計,處理多執行緒的併發問題(執行緒池)執行緒程式設計
- 【java 多執行緒】多執行緒併發同步問題及解決方法Java執行緒
- Java多執行緒-執行緒中止Java執行緒
- Java多執行緒——執行緒池Java執行緒
- 多執行緒與高併發(一)多執行緒入門執行緒
- 淺談執行緒池(中):獨立執行緒池的作用及IO執行緒池執行緒
- Java 併發:執行緒、執行緒池和執行器全面教程Java執行緒
- Java多執行緒/併發11、執行緒同步通訊:notify、waitJava執行緒AI
- Java併發(四)----執行緒執行原理Java執行緒
- 多執行緒應用執行緒
- Java多執行緒——獲取多個執行緒任務執行完的時間Java執行緒
- java多執行緒與併發 - 併發工具類Java執行緒
- Java多執行緒-執行緒池的使用Java執行緒
- 【Java多執行緒】執行緒安全的集合Java執行緒
- JAVA多執行緒和併發基礎Java執行緒
- Java併發/多執行緒-CAS原理分析Java執行緒
- 【多執行緒與高併發】Java守護執行緒是什麼?什麼是Java的守護執行緒?執行緒Java
- java併發與執行緒Java執行緒
- java 多執行緒守護執行緒Java執行緒
- Java多執行緒-執行緒通訊Java執行緒