天天看點

搞懂HashTable與ConcurrentHashMap前言一、如何處理HashMap在多線程中的安全問題二、如何實作線程安全三、HashTable與ConcurrentHashMap get與put過程四、1.8進行了什麼優化五、ConcurrentHashMap為什麼是高并發的六、CAS

搞懂HashTable與ConcurrentHashMap

  • 前言
  • 一、如何處理HashMap在多線程中的安全問題
  • 二、如何實作線程安全
    • 1.HashTable
    • 2.ConcurrentHashMap
    • 3.Collections.synchronizedMap(Map)
  • 三、HashTable與ConcurrentHashMap get與put過程
    • 1.HashTable的get:
    • 2.HashTable的put:
    • 3.ConcurrentHashMap的get:
    • 4.ConcurrentHashMap的put:
  • 四、1.8進行了什麼優化
  • 五、ConcurrentHashMap為什麼是高并發的
  • 六、CAS
    • 1.CAS原理:
    • 2.CAS防止ABA:

前言

HashMap在多線程環境下存線上程安全問題,本文将講解如何解決線程中安全問題

以下是本篇文章正文内容

一、如何處理HashMap在多線程中的安全問題

1.使用Collections.synchronizedMap(Map)建立線程安全的map集合; 2.Hashtable 3.ConcurrentHashMap 不過出于線程并發度的原因,都會舍棄前兩者使用最後的ConcurrentHashMap,他的性能和效率明顯 優于前兩者。

二、如何實作線程安全

1.HashTable

對資料操作的時候都會上鎖,導緻效率比較低下

代碼如下(示例):每次讀寫都會加鎖 最多允許一個線程通路并發量較低

搞懂HashTable與ConcurrentHashMap前言一、如何處理HashMap在多線程中的安全問題二、如何實作線程安全三、HashTable與ConcurrentHashMap get與put過程四、1.8進行了什麼優化五、ConcurrentHashMap為什麼是高并發的六、CAS

2.ConcurrentHashMap

具體原理請看 ConcurrentHashMap的put講解

1.7之前采用分段鎖的方式

1.8之後采用cas+synchronize

3.Collections.synchronizedMap(Map)

在SynchronizedMap内部維護了一個普通對象Map,還有排斥鎖mutex

建立出synchronizedMap之後,再操作map的時候,就會對方法上鎖

三、HashTable與ConcurrentHashMap get與put過程

1.HashTable的get:

首先計算key的下标,擷取hashtable中的數組
		找到對應下标數組,擷取對應的連結清單
		周遊倆表,判斷是否有滿足條件的entry,如果有,擷取value對應的值傳回。否則,傳回為空.
           

2.HashTable的put:

首先判斷value的是是否為空,如果為空,抛出空指針異常
	如果不為空,擷取hashtable中的table,擷取key的hash值以及對應的下标
	從數組中擷取下标對應的連結清單資訊,周遊連結清單,查詢是否有滿足條件的entry對象,如果有滿足條件的,舊值被新值覆寫,傳回舊值
	如果沒有,添加一個新的entry
	添加新的entry前先判斷目前數組的元素數量是否滿足擴容,如果滿足,進行擴容後,重新計算hash值和下标
	然後擷取目前已經存在的entry連結清單,然後建立的entry放在原連結清單的頭部,存進數組中.
	*在put方法中,如果需要向table[]中添加Entry元素,會首先進行容量校驗,如果容量已經達到了閥值,HashTable就會進行擴容處理rehash()
	*預設數組的長度是11, 擴容公式(oldsize * 2 + 1), key和value不能為null
	hashTable值不能為null的原因:會抛出控制原因   Hashtable使⽤的是安全失敗機制(fail-safe),這種機制會使你此次讀到的資料不一定是最新
	的資料。
           
搞懂HashTable與ConcurrentHashMap前言一、如何處理HashMap在多線程中的安全問題二、如何實作線程安全三、HashTable與ConcurrentHashMap get與put過程四、1.8進行了什麼優化五、ConcurrentHashMap為什麼是高并發的六、CAS

hashMap值能為null的原因

搞懂HashTable與ConcurrentHashMap前言一、如何處理HashMap在多線程中的安全問題二、如何實作線程安全三、HashTable與ConcurrentHashMap get與put過程四、1.8進行了什麼優化五、ConcurrentHashMap為什麼是高并發的六、CAS

3.ConcurrentHashMap的get:

key通過hash定位到具體segment,在通過一次Hash定位到具體的元素上。
           

4.ConcurrentHashMap的put:

1.計算出hashCode值
	2.判斷是否需要初始化	 無初始化則調用initTable()方法來進行初始化過程
	3.檢查有無沖突,無沖突則使用cas進行儲存,失敗則進行自旋
	4.判斷是否需要進行擴容
	5.不滿足條件進行synchronized寫入資料
	6.連結清單數量大于8進行紅黑樹轉換
	7.調用addCount()統計size判斷是否擴容
           

四、1.8進行了什麼優化

改進一: 取消segments字段,直接采用transient volatile HashEntry<K,V>[] table儲存資料,采用table數組元素作為鎖,進而實作了對每一行資料 進行加鎖,進			  一步減少并發沖突的機率。
	改進二: 将原先table數組+單向連結清單的資料結構,變更為table數組+單向連結清單+紅黑樹的結構。查詢的時間複雜度可以降低到O(logN)。
	JDK1.8的實作已經摒棄了Segment的概念,而是直接用Node數組+連結清單+紅黑樹的資料結構來實作,并發控制使用Synchronized和CAS來操作,整個看起來就
	是優化過且線程安全的HashMap,雖然在JDK1.8中還能看到Segment的資料結構,但是已經簡化了屬性,隻是為了相容舊版本。
	初始化操作并不是在構造函數實作的,而是在put操作中實作,當然ConcurrentHashMap還提供了其他的構造函數,有指定容量大小或者指定負載因子,跟
	HashMap一樣
           

五、ConcurrentHashMap為什麼是高并發的

1.7之前
ConcurrentHashMap 采用了分段鎖技術,其中 Segment 繼承于 ReentrantLock。
不會像 HashTable 那樣不管是 put 還是 get 操作都需要做同步處理,理論上 ConcurrentHashMap 
支援CurrencyLevel (Segment 數組數量)的線程并發。
每當一個線程占用鎖通路一個 Segment 時,不會影響到其他的 Segment。
就是說如果容量大小是16他的并發度就是16,可以同時允許16個線程操作16個Segment并且還是線程安全的。
他先定位到Segment,然後再進行put操作
1.8之後
其中抛棄了原有的 Segment 分段鎖,采用了 CAS + synchronized 來保證并發安全性。
跟HashMap很像,也把之前的HashEntry改成了Node,但是作用不變,把值和next采用了volatile去修飾,保證了可用性,并且也引用了紅黑樹,在連結清單大于阈定值的時候會轉換(預設是8)。
           

六、CAS

1.CAS原理:

CAS 是樂觀鎖的一種實作方式,是一種輕量級鎖,JUC 中很多工具類的實作就是基于 CAS 的。

CAS 操作的流程如下圖所示,線程在讀取資料時不進行加鎖,在準備寫回資料時,比較原值是否修改,

若未被其他線程修改則寫回,若已被修改,則重新執讀讀取流程。

這是一種樂觀政策,認為并發操作并不總會發生。

搞懂HashTable與ConcurrentHashMap前言一、如何處理HashMap在多線程中的安全問題二、如何實作線程安全三、HashTable與ConcurrentHashMap get與put過程四、1.8進行了什麼優化五、ConcurrentHashMap為什麼是高并發的六、CAS

2.CAS防止ABA:

一個線程把值改回了B,又來了一個線程把值改回了A,對于這個時候判斷的線程,就發現

他的值還是A,是以他就不知道這個值到底有沒有被⼈改過,其實很多場景如果隻追求最後結果正确,

這是沒關系的

解決方法

1.加版本号

2.加時間戳