1. 程式人生 > 其它 >java基礎之HashMap導致死迴圈以及ConcurrentHashMap和HashTable

java基礎之HashMap導致死迴圈以及ConcurrentHashMap和HashTable

HashMap多執行緒操作可能導致死迴圈問題

主要原因在於併發下的Rehash會造成元素之間形成一個迴圈連結串列。不過jdk1.8後解決了這個問題,但還是不建議在多執行緒下使用HashMap,因為多執行緒下使用HashMap還是會存在其他問題比如資料丟失。併發環境下推薦使用ConcurrentHashMap。

ConcurrentHashMap和Hashtable的區別

主要體現在實現執行緒安全的方式不同:

  • 底層資料結構:JDK1.7的ConcurrentHashMap底層採用分段陣列+連結串列 實現,JDK1.8採用的資料結構跟HashMap1.8的結構一樣,陣列+連結串列/紅黑二叉樹 。Hashtable和JDK1.8之前的HashMap的底層資料結構都是採用陣列+連結串列的形式,陣列是HashMap的主體,連結串列則是主要為了解決雜湊衝突而存在的。

  • 實現執行緒安全的方式: 在JDK1.7 的時候ConcurrentHashMap(分段鎖)對整個桶陣列進行了分割分段(Segment),每一把鎖只鎖容器其中一部分資料,多執行緒訪問容器裡不同資料段的資料,就不會存在鎖競爭,提高了併發訪問率 ,到了JDK1.8 的時候就已經摒棄了segment的概念,而是直接用Node陣列+連結串列+紅黑樹的結構來實現,併發控制使用synchronized和CAS來操作(JDK1.6以後對synchronized鎖做了很多優化 )整個看起來像是優化過且執行緒安全的HashMap,雖然在JDK1.8中還能看到Segment的資料結構,但已經簡化了屬性,只是為了相容舊版本;

    Hashtable(同一把鎖 ):使用synchronized來保證執行緒安全,效率非常低下。當一個執行緒訪問同步方法時,其他執行緒也訪問同步方法,可能會進入阻塞或輪詢 狀態,如果使用put 新增元素,另一個執行緒就不能使用put 新增元素,也不能用get ,競爭越激烈效率越低。

    ConcurrentHashMap執行緒安全的具體實現方式/底層具體實現

    JDK1.7如上圖

    首先將資料分成一段一段的儲存,然後給每段資料分配一把鎖,當一個執行緒佔用鎖訪問其中一段資料時,其他段的資料也能被其他執行緒訪問。

    ConcurrentHashMap是由Segment陣列結構和HashEnty陣列結構組成

    Segment實現了ReetrantLock,所以Sement是一種可重入鎖,扮演鎖的角色。HashEntry用於儲存鍵值對資料。

    一個ConurrentHashMap裡包含一個Segment陣列,Segment的結構和HashMap類似,是一種陣列和連結串列結構,一個Segment包含HashEntry陣列,每個HashEntry是一個連結串列結構的元素,每個Segment守護著一個HashEntry數組裡的元素,當對HashEntry陣列的資料進行修改時,必須先獲得對應的Segment的鎖。

    JDK1.8如上圖

    ConcurrentHashMap取消了Segment分段鎖,採用CAS和synchronized來保證併發安全。資料結構跟HashMap1.8的結構類似,陣列+連結串列/紅黑二叉樹。Java8在連結串列長度超過一定閾值(8 )時將連結串列轉換成紅黑樹

    synchronized只鎖定當前連結串列或紅黑二叉樹的首節點,這樣只要hash不衝突,就不會產生併發,效率又提升n倍