生活随笔
收集整理的這篇文章主要介紹了
Java - 线程安全的 HashMap 实现方法及原理
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
轉(zhuǎn)載自?http://liqianglv2005.iteye.com/blog/2025016
Java HashMap 是非線程安全的。在多線程條件下,容易導(dǎo)致死循環(huán),具體表現(xiàn)為CPU使用率100%。因此多線程環(huán)境下保證 HashMap 的線程安全性,主要有如下幾種方法:
?
使用 java.util.Hashtable 類,此類是線程安全的。使用 java.util.concurrent.ConcurrentHashMap,此類是線程安全的。使用 java.util.Collections.synchronizedMap() 方法包裝 HashMap object,得到線程安全的Map,并在此Map上進(jìn)行操作。自己在程序的關(guān)鍵方法或者代碼段加鎖,保證安全性,當(dāng)然這是嚴(yán)重的不推薦。
為什么 HashMap 非線程安全, 可以參考大神陳皓(weibo賬號:左耳朵耗子)在他自己技術(shù)網(wǎng)站Coolshell上的文章,寫的非常詳細(xì)。文章鏈接(直通車):http://coolshell.cn/articles/9606.html
?
?
這里重點分析下上面列舉的幾種方法實現(xiàn)并行安全性的原理:
?
(一)java.util.Hashtable類:類的主要數(shù)據(jù)結(jié)構(gòu)如下:
????
Java代碼??
????? ? ?? ????private?transient?Entry[]?table;?? ?? private?static?class?Entry<K,V>?implements?Map.Entry<K,V>?{?? ????int?hash;?? ????K?key;?? ????V?value;?? ????Entry<K,V>?next;??
? ???
?????? 可見,Hashtable 的實現(xiàn)是一個數(shù)組,每個數(shù)組元素是一個LinkList結(jié)構(gòu),因此類的數(shù)據(jù)實際上保存在一個散列表中。這個實現(xiàn)和 HashMap 的實現(xiàn)是一致的。數(shù)據(jù)結(jié)構(gòu)如下:
?
????? 那么Hashtable如何保證線程安全性的哪?下面是 Hashtable的源碼:
?
Java代碼??
public?synchronized?V?get(Object?key)?{?? ????Entry?tab[]?=?table;?? ????……??? ????}?? ?? public?synchronized?V?put(K?key,?V?value)?{?? ????……??? ????}?? ?? ?? public?synchronized?V?remove(Object?key)?{?? ????……??? ????}?? ?? ????? ????public?synchronized?void?putAll(Map<??extends?K,???extends?V>?t)?{?? ????????for?(Map.Entry<??extends?K,???extends?V>?e?:?t.entrySet())?? ????????????put(e.getKey(),?e.getValue());?? ????}?? ?? ?? ????public?synchronized?void?clear()?{?? ????……??? ????}??
???? 上面是 Hashtable 提供的幾個主要方法,包括 get(), put(), remove() 等。注意到每個方法本身都是 synchronized 的,不會出現(xiàn)兩個線程同時對數(shù)據(jù)進(jìn)行操作的情況,因此保證了線程安全性,但是也大大的降低了執(zhí)行效率。因此是不推薦的。
?
?
(二)使用 java.util.Collections.synchronizedMap(Map<K,V>) 方法進(jìn)行封裝。 方法源代碼如下:
?
Java代碼??
public?static?<K,V>?Map<K,V>?synchronizedMap(Map<K,V>?m)?{?? ????return?new?SynchronizedMap<K,V>(m);?? ????}?? ?? ?? private?static?class?SynchronizedMap<K,V>?? ????implements?Map<K,V>,?Serializable?{?? ?????? ????private?static?final?long?serialVersionUID?=?1978198479659022715L;?? ?? ????private?final?Map<K,V>?m;??????? ????????final?Object??????mutex;?????? ?? ????SynchronizedMap(Map<K,V>?m)?{?? ????????????if?(m==null)?? ????????????????throw?new?NullPointerException();?? ????????????this.m?=?m;?? ????????????mutex?=?this;?? ????????}?? ?? ????SynchronizedMap(Map<K,V>?m,?Object?mutex)?{?? ????????????this.m?=?m;?? ????????????this.mutex?=?mutex;?? ????????}?? ?? ????public?int?size()?{?? ????????synchronized(mutex)?{return?m.size();}?? ????????}?? ????public?boolean?isEmpty(){?? ????????synchronized(mutex)?{return?m.isEmpty();}?? ????????}?? ????public?boolean?containsKey(Object?key)?{?? ????????synchronized(mutex)?{return?m.containsKey(key);}?? ????????}??
?????? 從實現(xiàn)源代碼可以發(fā)現(xiàn),其封裝的本質(zhì)和 Hashtable 的實現(xiàn)是完全一致的,即對原Map本身的方法進(jìn)行加鎖,加鎖的對象或者為外部指定共享對象mutex,或者為包裝后的線程安全的Map本身。Hashtable 可以理解為 SynchronizedMap mutex=null 時候的特殊情況。因此這種同步方式的執(zhí)行效率也是很低的。
???
??????? 既然已經(jīng)有了Hashtable, 為什么還需要Collections 提供的這種靜態(tài)方法包裝哪?很簡單,這種包裝是Java Collection Framework提供的統(tǒng)一接口,除了用于 HashMap 外,還可以用于其他的Map。當(dāng)然 除了對Map進(jìn)行封裝,Collections工具類還提供了對 Collection(比如Set,List)的線程安全實現(xiàn)封裝方法,具體請參考 java.util.Colletions 實現(xiàn),其原理和 SynchronizedMap 是一致的。
?
(三) 使用 java.util.concurrent.ConcurrentHashMap 類。并發(fā)編程大師 Doug Lea 出品,絕對精品。這是 HashMap 的線程安全版,同 Hashtable 相比,ConcurrentHashMap 不僅保證了訪問的線程安全性,而且在效率上有較大的提高。
?
ConcurrentHashMap的數(shù)據(jù)結(jié)構(gòu)如下(引用圖片地址http://www.yupoo.com/photos/goldendoc/81556254/):
?
可以看出,相對 HashMap 和 Hashtable, ConcurrentHashMap 增加了Segment 層,每個Segment 原理上等同于一個 Hashtable, ConcurrentHashMap 為 Segment 的數(shù)組。下面是 ConcurrentHashMap 的 put 和 get 方法:
Java代碼??
final?Segment<K,V>?segmentFor(int?hash)?{?? ????????return?segments[(hash?>>>?segmentShift)?&?segmentMask];?? ????}?? ?? public?V?put(K?key,?V?value)?{?? ????????if?(value?==?null)?? ????????????throw?new?NullPointerException();?? ????????int?hash?=?hash(key.hashCode());?? ????????return?segmentFor(hash).put(key,?hash,?value,?false);?? ????}?? ?? public?V?get(Object?key)?{?? ????????int?hash?=?hash(key.hashCode());?? ????????return?segmentFor(hash).get(key,?hash);?? ????}??
向 ConcurrentHashMap 中插入數(shù)據(jù)或者讀取數(shù)據(jù),首先都要講相應(yīng)的 Key 映射到對應(yīng)的 Segment,因此不用鎖定整個類, 只要對單個的 Segment 操作進(jìn)行上鎖操作就可以了。理論上如果有 n 個 Segment,那么最多可以同時支持 n 個線程的并發(fā)訪問,從而大大提高了并發(fā)訪問的效率。另外?rehash() 操作也是對單個的 Segment 進(jìn)行的,所以由 Map 中的數(shù)據(jù)量增加導(dǎo)致的 rehash 的成本也是比較低的。
?
單個 Segment 的進(jìn)行數(shù)據(jù)操作的源碼如下:
?
Java代碼??
V?put(K?key,?int?hash,?V?value,?boolean?onlyIfAbsent)?{?? ????????????lock();?? ????????????try?{?? ????????????????int?c?=?count;?? ????????????????if?(c++?>?threshold)??? ????????????????????rehash();?? ?? ?????????????????……??? ?? ????????????}?finally?{?? ????????????????unlock();?? ????????????}?? ????????}?? ?? V?replace(K?key,?int?hash,?V?newValue)?{?? ????????????lock();?? ????????????try?{?? ????????????????HashEntry<K,V>?e?=?getFirst(hash);?? ???????????????? ?????????????????……??? ????????????????? ????????????}?finally?{?? ????????????????unlock();?? ????????????}?? ????????}??
?
?? 可見對 單個的 Segment 進(jìn)行的數(shù)據(jù)更新操作都是 加鎖的,從而能夠保證線程的安全性。
?
?
ConcurrentHashMap 的更具體實現(xiàn)和分析見(直通車)?http://www.iteye.com/topic/1103980, 非常詳細(xì)。
幾種線程同步實現(xiàn)方法的效率比較,可以參考(直通車)??http://blog.sina.com.cn/s/blog_734a77160100yku1.html
總結(jié)
以上是生活随笔為你收集整理的Java - 线程安全的 HashMap 实现方法及原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。