首頁>Club>
11
回覆列表
  • 1 # Java從入門到架構師

    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,併發修改後快速失敗。

  • 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來換取資料的安全性!

  • 中秋節和大豐收的關聯?
  • 《格林童話》為什麼出名,僅僅是因為童話嗎,對於現在長大的你來說是否重新認識了童話?