map的線程安全問題

爲何HashMap是線程不安全的

總說 HashMap 是線程不安全的,不安全的,不安全的,那麼到底爲何它是線程不安全的呢?要回答這個問題就要先來簡單瞭解一下 HashMap 源碼中的使用的存儲結構(這裏引用的是 Java 8 的源碼,與7是不同的)和它的擴容機制java

HashMap 內部存儲使用了一個 Node 數組(默認大小是16),而 Node 類包含一個類型爲 Node 的 next 的變量,也就是至關於一個鏈表,全部根據 hash 值計算的 bucket 同樣的 key 會存儲到同一個鏈表裏(即產生了衝突)。算法

 

HashMap的自動擴容機制

HashMap 內部的 Node 數組默認的大小是16,假設有100萬個元素,那麼最好的狀況下每一個 hash 桶裏都有62500個元素,這時get(),put(),remove()等方法效率都會下降。爲了解決這個問題,HashMap 提供了自動擴容機制,當元素個數達到數組大小 loadFactor 後會擴大數組的大小,在默認狀況下,數組大小爲16,loadFactor 爲0.75,也就是說當 HashMap 中的元素超過16\0.75=12時,會把數組大小擴展爲2*16=32,而且從新計算每一個元素在新數組中的位置。編程

爲何線程不安全

我的以爲 HashMap 在併發時可能出現的問題主要是兩方面,首先若是多個線程同時使用put方法添加元素,並且假設正好存在兩個 put 的 key 發生了碰撞(根據 hash 值計算的 bucket 同樣),那麼根據 HashMap 的實現,這兩個 key 會添加到數組的同一個位置,這樣最終就會發生其中一個線程的 put 的數據被覆蓋。第二就是若是多個線程同時檢測到元素個數超過數組大小* loadFactor ,這樣就會發生多個線程同時對 Node 數組進行擴容,都在從新計算元素位置以及複製數據,可是最終只有一個線程擴容後的數組會賦給 table,也就是說其餘線程的都會丟失,而且各自線程 put 的數據也丟失。數組

《Java併發編程的藝術》一書中是這樣說的:HashMap 在併發執行 put 操做時會引發死循環,致使 CPU 利用率接近100%。由於多線程會致使 HashMap 的 Node 鏈表造成環形數據結構,一旦造成環形數據結構,Node 的 next 節點永遠不爲空,就會在獲取 Node 時產生死循環。安全

死循環並非發生在 put 操做時,而是發生在擴容時。數據結構

如何線程安全的使用HashMap

瞭解了 HashMap 爲何線程不安全,那如今看看如何線程安全的使用 HashMap。這個無非就是如下三種方式:多線程

  • Hashtable
  • ConcurrentHashMap
  • Synchronized Map

ConcurrentHashMap

ConcurrentHashMap (如下簡稱CHM)是 JUC 包中的一個類,Spring 的源碼中有不少使用 CHM 的地方。以前已經翻譯過一篇關於 ConcurrentHashMap 的博客,如何在java中使用ConcurrentHashMap,裏面介紹了 CHM 在 Java 中的實現,CHM 的一些重要特性和什麼狀況下應該使用 CHM。須要注意的是,上面博客是基於 Java 7 的,和8有區別,在8中 CHM 摒棄了 Segment(鎖段)的概念,而是啓用了一種全新的方式實現,利用 CAS 算法,有時間會從新總結一下。併發

相關文章
相關標籤/搜索