-
1 # Java從入門到架構師
-
2 # 遇見筱雅君
HashMap是執行緒不安全的,如果有多個執行緒同時寫資料,有可能會使得資料不一致。
如果需要滿足執行緒安全,可以用 Collections的synchronizedMap方法使HashMap具有執行緒安全的能力,或者使用ConcurrentHashMap。
具體使用方法:
//synchronizedMap
Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());
//ConcurrentHashMap
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
-
3 # 會點程式碼的大叔
不安全。
非執行緒安全併發場景中如果不保持足夠的同步,可能會在執行HashMap.get時進入死迴圈。高併發場景中,使用ConcurrentHashMap;採用陣列方式儲存key.value構成的Entry物件,無容量限制;基於key hash尋找Entry物件儲存到陣列中的位置,對於hash衝突採用連結串列方式解決;插入元素時可能要擴大陣列的容量,需要重新計算hash,並複製新物件到陣列中。
HashMap建立:HashMap():預設是建立loadFactor = 0.75 ,threshold = 12 ,大小為16的Entry物件陣列。增加:put(Object key , Object value)key = null ,遍歷,找到則將新的value賦值;如果沒有找到則增減一個Entry物件,key為null,value為傳入物件,next指向當前陣列的第一個物件;
如果當前陣列使用大小 >= threshold,則將陣列擴大為當前大小的兩倍,擴大時對當前Entry物件陣列中的元素重新hash,並填充陣列,重新設定threshold值。
獲取單個物件:get(Object key)
!=null時,對key進行按位與操作,找到對應的儲存位置,找到此位置對應的Entry物件,基於next屬性遍歷,需要到hash值相等,且key值相等並或equals的Entry物件,返回其value,未找到返回null。
與get類似
判斷物件是否存在:containsKey( Object key )
呼叫getEntry方法完成,過程與get過程基本相同。
遍歷:keySet()
遍歷時,無法保證順序
-
4 # 此生唯一
hashMap是否執行緒安全基本上是在每次面試都會問的了,而99%的JAVA程式設計師都知道hashMap是非執行緒安全的,不過知道其底層原因的應該不多,下面來說下為什麼是執行緒不安全的!
我們都知道,hashMap是一種在開發中最常用的資料結構(key-value型),因為它很快(O(1)常數級別的查詢),在儲存的時候透過計算key的hash值,將value存在對應的桶裡(陣列的一個元素),然後透過頭插法插入桶中的單向連結串列裡,如下圖所示:
這個過程是執行緒安全的,因為就算是發生了執行緒同享資源,無非就是插入的資料順序問題而已,無傷大雅,但是我們都知道,hashMap為了防止資料查詢過慢(如果單鏈表中的資料過大,相當於O(1)常數級別的效能下降為O(N)線性級別),採取了自動擴容的方式,一旦儲存資料的大小size超過了總容量的0.75(裝載因子),就發生自動擴容,安全問題也就隨之誕生了!
hashMap怎麼實現擴容呢?一旦需要擴容的時候,會新建一個兩倍容量的hashMap,並把原來的元素重新做hash存入新的hash陣列,底層原始碼使用的resize方法:
在resize方法中需要呼叫transfer方法,方法很簡單,無非就是遍歷老hash陣列,然後重新放入新的hash陣列中,如下:
當thread1執行到Entry<k,v> next =e.next的時候,把原來的key7指向key3,變為了key3指向key7,因為java是搶佔式執行緒,此時thread2開始執行,不過線上程2中的快照中,還是key7指向key3(但實質key3已經指向了key7)!
經過執行緒2的處理完成,執行緒繼續處理,這個時候key3指向key7,反過來key7也指向key3,這時候單鏈表變成了環形連結串列:
等到查詢方法查詢到這個hash陣列的時候,查詢出現了死迴圈,永遠卡死在這,CPU跑滿。
所以hashMap是非執行緒安全的,相對應的hashTable擁有著和hashMap類似的結構,但是因為hashTable中的所有方法都加了鎖(synchonize),所以在多執行緒處理中,應該是用hashTable來換取資料的安全性!
回覆列表
1.同步HashMap – ConcurrentHashMap
ConcurrentHashMap如果我們希望在併發環境中使用Map,那麼我們的首選應該始終是使用該類。ConcurrentHashMap支援透過設計併發訪問其鍵值對。我們無需執行任何其他程式碼修改即可在地圖上啟用同步。
請注意,從獲得的迭代器ConcurrentHashMap不會丟擲ConcurrentModificationException。但是,迭代器被設計為一次只能由一個執行緒使用。這意味著我們從ConcurrentHashMap獲得的每個迭代器都設計為由單個執行緒使用,並且不應傳遞。
如果這樣做,則無法保證一個執行緒會看到另一執行緒執行的對映更改(無需從對映中獲取新的迭代器)。該迭代器保證它的創作時間,以反映在地圖的狀態。
讓我們舉一個使用ConcurrentHashMap的例子。
ConcurrentHashMap示例
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
public class HashMapExample
{
public static void main(String[] args) throws CloneNotSupportedException
{
ConcurrentHashMap<Integer, String> concurrHashMap = new ConcurrentHashMap<>();
//Put require no synchronization
concurrHashMap.put(1, "A");
concurrHashMap.put(2, "B");
//Get require no synchronization
concurrHashMap.get(1);
Iterator<Integer> itr = concurrHashMap.keySet().iterator();
//Using synchronized block is advisable
synchronized (concurrHashMap)
{
while(itr.hasNext()) {
System.out.println(concurrHashMap.get(itr.next()));
}
}
}
}
程式輸出:
A
B
2.同步HashMap – Collections.synchronizedMap()
同步HashMap的工作原理也與ConcurrentHashMap非常相似,只是沒有什麼區別。
SynchronizedHashMap因為所有方法都宣告為sync,所以is一次只允許一個執行緒執行讀/寫操作。ConcurrentHashMap允許多個執行緒在地圖中的不同段上獨立工作。這樣可以在ConcurrentHashMap中實現更高程度的併發,從而提高整個應用程式的效能。
來自兩個類的迭代器都應該在synchronizedblock 內部使用,但是來自SynchronizedHashMap的迭代器是快速失敗的。而ConcurrentHashMap迭代器就是不是快速失敗的。
SynchronizedHashMap示例
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class HashMapExample
{
public static void main(String[] args) throws CloneNotSupportedException
{
Map<Integer, String> syncHashMap = Collections.synchronizedMap(new HashMap<>());
//Put require no synchronization
syncHashMap.put(1, "A");
syncHashMap.put(2, "B");
//Get require no synchronization
syncHashMap.get(1);
Iterator<Integer> itr = syncHashMap.keySet().iterator();
//Using synchronized block is advisable
synchronized (syncHashMap)
{
while(itr.hasNext()) {
System.out.println(syncHashMap.get(itr.next()));
}
}
}
}
程式輸出:
A
B
3.同步HashMap和ConcurrentHashMap之間的區別
讓我們找出兩種地圖版本之間的差異,以便我們決定在哪種條件下選擇哪一種。
無需鎖定地圖即可讀取ConcurrentHashMap中的值。檢索操作將返回由最近完成的插入操作插入的值。SynchronizedHashMap中的讀取操作也是需要一個鎖的。
ConcurrentModificationException假如一個執行緒試圖修改它,而同時另一個執行緒對其進行迭代,則ConcurrentHashMap不會丟擲。迭代器會在建立地圖時反映其狀態。SynchronizedHashMap返回Iterator,併發修改後快速失敗。