map的線程安全問題


為什么HashMap是線程不安全的

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

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 算法,有時間會重新總結一下。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM