探究HashMap線性不安全(三)——死循環的產生


內容

  網上很多資料都詳細地講解了HashMap底層的實現,但是講到HashMap的並發操作不是線性安全時,往往一筆帶過:在多個線程並發擴容時,會在執行transfer()方法轉移鍵值對時,造成鏈表成環,導致程序在執行get操作時形成死循環

​  對於沒有研究過該過程的童鞋,很難費解這句話的含義。下面筆者分四個小節帶着大家共同研究一下JDK1.7和JDK1.8版本下HashMap的線性不安全是怎么造成的,詳細探究鏈表成環的形成過程。如果對於HashMap底層的put、get操作不清楚,建議先學習參考1中的內容。

適合人群

​  Java進階

說明

  轉載請說明出處:探究HashMap線性不安全(三)——死循環的產生

參考

​ 1、https://www.toutiao.com/i6544826418210013700/ HashMap底層數據結構原理

​ 2、https://www.toutiao.com/i6545790064104833539/ 為什么HashMap非線程安全

​ 3、https://blog.csdn.net/qq_32182461/article/details/81152025 hashmap並發情況下的成環原因(筆者認為該文是一種誤解)

正文

​  本節將探究環形鏈表是如何在hashMap查詢時產生死循環的。

​  以上節為例,當hashMap對象查找一個不為空的Key時,會執行getEntry(key)方法。

1 public V get(Object key) { 2         //key為null時,從index為0的鏈表中查找數據
3         if (key == null) 4             return getForNullKey(); 5         //key不為null是查找數據 
6         Entry<K,V> entry = getEntry(key); 7         //如果未查找到key對應的值,那么entry為null,get方法會返回null。
8         return null == entry ? null : entry.getValue(); 9     }

1538216304142

​  getEntry(key)方法會計算Key的hash值,然后通過indexFor(hash, table.length)對hash值取模求得key對應table數組的下標index。如果這個Key對應的table數組下標為3,那么線程會在下標3處的環形鏈表上遍歷檢索目標key對應的值。當key對應的鍵值對不存在,線程將進入死循環,代碼如下所示:

 1 final Entry<K,V> getEntry(Object key) {  2     //計算key的hash值
 3     int hash = (key == null) ? 0 : hash(key);  4     //通過indexFor(hash, table.length)求得key對應的index,然后遍歷index下表的鏈表
 5     for (Entry<K,V> e = table[indexFor(hash, table.length)];  6          e != null;  7          e = e.next) {  8  Object k;  9         //如果鏈表中不存在對應key的鍵值對,且鏈表為環形,那么當前線程將在for語句中產生死循環。
10         if (e.hash == hash &&
11             ((k = e.key) == key || (key != null && key.equals(k)))) 12             return e; 13  } 14     return null; 15 }

  下面寫一個​測試方法,使用多線程對HashMap進行並發寫操作,並進行單線程讀來演示死循環的產生。

 1 public class TestMain {  2     public static void main(String[] args) throws InterruptedException {  3         final HashMap<String, String> map = new HashMap<>();  4         System.out.println("begin");  5         int num = 15;  6         final CountDownLatch countDownLatch = new CountDownLatch(num);  7         //開始並發put操作
 8         for (int i = 0; i < num; i++) {  9             final Integer integer = i; 10             new Thread() { 11  @Override 12                 public void run() { 13                     for (int j = 0; j < 100; j++) { 14                         map.put("a" + integer + j, "b" + integer + j); 15  } 16  countDownLatch.countDown(); 17  } 18  }.start(); 19  } 20         //等待所有線程完成寫入操作 
21  countDownLatch.await(); 22  System.out.println(map.size()); 23         //循環讀取map中不存在的key,如果生成了環形鏈表,當key的hash值匹配到該鏈表時,發生死循環
24         for (int m = 0; m < 200; m++) { 25             System.out.println("c" + m + ":  " + map.get("c" + m)); 26  } 27  } 28 } 

  截圖如下(筆者大概實驗了10次,模擬出次故障):

  至此,JDK1.7中HashMap的線性不安全特性已經論證完畢。下一節,將探究JDK1.8中HashMap的線性不安全特性。


免責聲明!

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



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