Java集合--ConcurrentMap
1 Map併發集合
1.1 ConcurrentMap
ConcurrentMap,它是一個介面,是一個能夠支援併發訪問的java.util.map集合;
在原有java.util.map介面基礎上又新提供了4種方法,進一步擴充套件了原有Map的功能:
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);
}
putIfAbsent:與原有put方法不同的是,putIfAbsent方法中如果插入的key相同,則不替換原有的value值;
remove:與原有remove方法不同的是,新remove方法中增加了對value的判斷,如果要刪除的key--value不能與Map中原有的key--value對應上,則不會刪除該元素;
replace(K,V,V):增加了對value值的判斷,如果key--oldValue能與Map中原有的key--value對應上,才進行替換操作;
replace(K,V):與上面的replace不同的是,此replace不會對Map中原有的key--value進行比較,如果key存在則直接替換;
其實,對於ConcurrentMap來說,我們更關注Map本身的操作,在併發情況下是如何實現資料安全的。在java.util.concurrent包中,ConcurrentMap的實現類主要以ConcurrentHashMap為主。接下來,我們具體來看下。
1.2 ConcurrentHashMap
ConcurrentHashMap是一個執行緒安全,並且是一個高效的HashMap。
但是,如果從執行緒安全的角度來說,HashTable已經是一個執行緒安全的HashMap,那推出ConcurrentHashMap的意義又是什麼呢?
說起ConcurrentHashMap,就不得不先提及下HashMap線上程不安全的表現,以及HashTable的效率!
- HashMap
關於HashMap的講解,在此前的文章中已經說過了,本篇不在做過多的描述,有興趣的朋友可以來這裡看下--HashMap。
在此節中,我們主要來說下,在多執行緒情況下HashMap的表現?
HashMap中新增元素的原始碼:(基於JDK1.7.0_45)
public V put(K key, V value) {
。。。忽略
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
。。。忽略
createEntry(hash, key, value, bucketIndex);
}
//向連結串列頭部插入元素:在陣列的某一個角標下形成連結串列結構;
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
在多執行緒情況下,同時A、B兩個執行緒走到createEntry()方法中,並且這兩個執行緒中插入的元素hash值相同,bucketIndex值也相同,那麼無論A執行緒先執行,還是B執行緒先被執行,最終都會2個元素先後向連結串列的頭部插入,導致互相覆蓋,致使其中1個執行緒中的資料丟失。這樣就造成了HashMap的執行緒不安全,資料的不一致;
更要命的是,HashMap在多執行緒情況下還會出現死迴圈的可能,造成CPU佔用率升高,導致系統卡死。
舉個簡單的例子:
public class ConcurrentHashMapTest {
public static void main(String[] agrs) throws InterruptedException {
final HashMap<String,String> map = new HashMap<String,String>();
Thread t = new Thread(new Runnable(){
public void run(){
for(int x=0;x<10000;x++){
Thread tt = new Thread(new Runnable(){
public void run(){
map.put(UUID.randomUUID().toString(),"");
}
});
tt.start();
System.out.println(tt.getName());
}
}
});
t.start();
t.join();
}
}
在上面的例子中,我們利用for迴圈,啟動了10000個執行緒,每個執行緒都向共享變數中新增一個元素。
測試結果:通過使用JDK自帶的jconsole工具,可以看到HashMap內部形成了死迴圈,並且主要集中在兩處程式碼上。
那麼,是什麼原因造成了死迴圈?
HashMap--put()494行:(基於JDK1.7.0_45)
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {------**for迴圈494行**
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
HashMap--transfer()601行:(基於JDK1.7.0_45)
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}-----**while迴圈601行**
}
}
通過檢視程式碼,可以看出,死迴圈的產生:主要因為在遍歷陣列角標下的連結串列時,沒有了為null的元素,單向連結串列變成了迴圈連結串列,頭尾相連了。
以上兩點,就是HashMap在多執行緒情況下的表現。
- HashTable
說完了HashMap的執行緒不安全,接下來說下HashTable的效率!!
HashTable與HashMap的結構一致,都是雜湊表實現。
與HashMap不同的是,在HashTable中,所有的方法都加上了synchronized鎖,用鎖來實現執行緒的安全性。由於synchronized鎖加在了HashTable的每一個方法上,所以這個鎖就是HashTable本身--this。那麼,可想而知HashTable的效率是如何,安全是保證了,但是效率卻損失了。
無論執行哪個方法,整個雜湊表都會被鎖住,只有其中一個執行緒執行完畢,釋放所,下一個執行緒才會執行。無論你是呼叫get方法,還是put方法皆是如此;
HashTable部分原始碼:(基於JDK1.7.0_45)
public class Hashtable<K,V> extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
public synchronized int size() {...}
public synchronized boolean isEmpty() {...}
public synchronized V get(Object key) {...}
public synchronized V put(K key, V value) {...}
}
通過上述程式碼,可以清晰看出,在HashTable中的主要操作方法上都加了synchronized鎖以來保證執行緒安全。
說完了HashMap和HashTable,下面我們就重點介紹下ConcurrentHashMap,看看ConcurrentHashMap是如何來解決上述的兩個問題的!
1.3 ConcurrentHashMap結構
在說到ConcurrentHashMap原始碼之前,我們首先來了解下ConcurrentHashMap的整體結構,這樣有利於我們快速理解原始碼。
不知道,大家還是否記得HashMap的整體結構呢?如果忘記的話,我們就在此進行回顧下!
HashMap底層使用陣列和連結串列,實現雜湊表結構。插入的元素通過雜湊的形式分佈到陣列的各個角標下;當有重複的雜湊值時,便將新增的元素插入在連結串列頭部,使其形成連結串列結構,依次向後排列。
下面是,ConcurrentHashMap的結構:
與HashMap不同的是,ConcurrentHashMap中多了一層陣列結構,由Segment和HashEntry兩個陣列組成。其中Segment起到了加鎖同步的作用,而HashEntry則起到了儲存K.V鍵值對的作用。
在ConcurrentHashMap中,每一個ConcurrentHashMap都包含了一個Segment陣列,在Segment陣列中每一個Segment物件則又包含了一個HashEntry陣列,而在HashEntry陣列中,每一個HashEntry物件儲存K-V資料的同時又形成了連結串列結構,此時與HashMap結構相同。
在多執行緒中,每一個Segment物件守護了一個HashEntry陣列,當對ConcurrentHashMap中的元素修改時,在獲取到對應的Segment陣列角標後,都會對此Segment物件加鎖,之後再去操作後面的HashEntry元素,這樣每一個Segment物件下,都形成了一個小小的HashMap,在保證資料安全性的同時,又提高了同步的效率。只要不是操作同一個Segment物件的話,就不會出現執行緒等待的問題!
相關文章
- Java 8 併發: 原子變數和 ConcurrentMapJava變數
- 【Java集合】1 集合概述Java
- 【Java集合原始碼剖析】Java集合框架Java原始碼框架
- 【JAVA集合】JAVA集合框架及其常用方法Java框架
- java——集合Java
- java集合Java
- Java集合詳解(一):全面理解Java集合Java
- Java集合(1)一 集合框架Java框架
- Java集合——ArrayListJava
- JAVA_集合Java
- Java 集合框架Java框架
- Java集合:HashMapJavaHashMap
- Java 集合概述Java
- 集合類【Java】Java
- Java集合大全Java
- java集合-ListJava
- Java集合-CollectionJava
- java集合概述Java
- Java新集合Java
- Java 集合 ListJava
- 探究Java集合Java
- JAVA集合-ArrayListJava
- 【java】【集合】TreeSetJava
- Java集合(四)Java
- 六,Java集合Java
- java集合框架Java框架
- 【集合框架】Java集合框架綜述框架Java
- Java集合類初探Java
- Java集合類——MapJava
- Java 集合之ArrayListJava
- Java集合之HashMapJavaHashMap
- Java集合之ArrayListJava
- java集合之CopyOnWriteArrayListJava
- Java集合系列-HashSetJava
- JAVA集合——Map介面Java
- java集合統述Java
- java集合腦圖Java
- java-集合-3Java