ConcurrentHashMap竟然也有死迴圈問題?

来源:https://www.cnblogs.com/luoxn28/archive/2019/06/22/11068316.html
-Advertisement-
Play Games

前幾天和朋友閑聊,說遇到了一個ConcurrentHashMap死迴圈問題,當時心裡想這不科學呀?ConcurrentHashMap怎麼還有死迴圈呢,畢竟它已經解決HashMap中rehash中死迴圈問題了,但是隨著深入的分析,發現事情並沒有之前想的那麼簡單~ (以下分析基於jdk版本:jdk1.8 ...


前幾天和朋友閑聊,說遇到了一個ConcurrentHashMap死迴圈問題,當時心裡想這不科學呀?ConcurrentHashMap怎麼還有死迴圈呢,畢竟它已經解決HashMap中rehash中死迴圈問題了,但是隨著深入的分析,發現事情並沒有之前想的那麼簡單~ (以下分析基於jdk版本:jdk1.8.0_171)

保險起見,不能直接貼出出現問題的業務代碼,因此將該問題簡化成如下代碼:

ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>();
// map預設capacity 16,當元素個數達到(capacity - capacity >> 2) = 12個時會觸發rehash
for (int i = 0; i < 11; i++) {
    map.put(i, i);
}

map.computeIfAbsent(12, (k) -> {
    // 這裡會導致死迴圈 :(
    map.put(100, 100);
    return k;
});

// 其他操作

感興趣的小伙伴可以在電腦上運行下,話不說多,先說下問題原因:當執行computeIfAbsent時,如果key對應的slot為空,此時會創建ReservationNode對象(hash值為RESERVED=-3)放到當前slot位置,然後調用mappingFunction.apply(key)生成value,根據value創建Node之後賦值到slow位置,此時完成computeIfAbsent流程。但是上述代碼mappingFunction中又對該map進行了一次put操作,並且觸發了rehash操作,在transfer中遍歷slot數組時,依次判斷slot對應Node是否為null、hash值是否為MOVED=-1、hash值否大於0(list結構)、Node類型是否是TreeBin(紅黑樹結構),唯獨沒有判斷hash值為RESERVED=-3的情況,因此導致了死迴圈問題。

問題分析到這裡,原因已經很清楚了,當時我們認為,這可能是jdk的“bug”,因此我們最後給出的解決方案是:

  1. 如果在rehash時出現了slot節點類型是ReservationNode,可以給個提示,比如拋異常;
  2. 理論上來說,mappingFunction中不應該再對當前map進行更新操作了,但是jdk並沒有禁止不能這樣用,最好說明下。

最後,另一個朋友看了computeIfAbsent的註釋:

 1 /**
 2  * If the specified key is not already associated with a value,
 3  * attempts to compute its value using the given mapping function
 4  * and enters it into this map unless {@code null}.  The entire
 5  * method invocation is performed atomically, so the function is
 6  * applied at most once per key.  Some attempted update operations
 7  * on this map by other threads may be blocked while computation
 8  * is in progress, so the computation should be short and simple,
 9  * and must not attempt to update any other mappings of this map.
10  */
11 public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)

我們發現,其實人家已經知道了這個問題,還特意註釋說明瞭。。。我們還是too yong too simple啊。至此,ConcurrentHashMap死迴圈問題告一段落,還是要遵循編碼規範,不要在mappingFunction中再對當前map進行更新操作。其實ConcurrentHashMap死迴圈不僅僅出現在上述討論的場景中,以下場景也會觸發,原因和上述討論的是一樣的,代碼如下,感興趣的小伙伴也可以本地跑下:

1 ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>();
2 map.computeIfAbsent(12, (k) -> {
3     map.put(k, k);
4     return k;
5 });
6 
7 System.out.println(map);
8 // 其他操作

最後,一起跟著computeIfAbsent源碼來分下上述死迴圈代碼的執行流程,限於篇幅,只分析下主要流程代碼:

 1 public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
 2     if (key == null || mappingFunction == null)
 3         throw new NullPointerException();
 4     int h = spread(key.hashCode());
 5     V val = null;
 6     int binCount = 0;
 7     for (Node<K,V>[] tab = table;;) {
 8         Node<K,V> f; int n, i, fh;
 9         if (tab == null || (n = tab.length) == 0)
10             tab = initTable();
11         else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
12             Node<K,V> r = new ReservationNode<K,V>();
13             synchronized (r) {
14                 // 這裡使用synchronized針對局部對象意義不大,主要是下麵的cas操作保證併發問題
15                 if (casTabAt(tab, i, null, r)) {
16                     binCount = 1;
17                     Node<K,V> node = null;
18                     try {
19                         // 這裡的value返回可能為null呦
20                         if ((val = mappingFunction.apply(key)) != null)
21                             node = new Node<K,V>(h, key, val, null);
22                     } finally {
23                         setTabAt(tab, i, node);
24                     }
25                 }
26             }
27             if (binCount != 0)
28                 break;
29         }
30         else if ((fh = f.hash) == MOVED)
31             tab = helpTransfer(tab, f);
32         else {
33             boolean added = false;
34             synchronized (f) {
35                 // 僅僅判斷了node.hash >=0和node為TreeBin類型情況,未判斷`ReservationNode`類型
36                 // 擴容時判斷和此處類似
37                 if (tabAt(tab, i) == f) {
38                     if (fh >= 0) {
39                         binCount = 1;
40                         for (Node<K,V> e = f;; ++binCount) {
41                             K ek; V ev;
42                             if (e.hash == h &&
43                                 ((ek = e.key) == key ||
44                                  (ek != null && key.equals(ek)))) {
45                                 val = e.val;
46                                 break;
47                             }
48                             Node<K,V> pred = e;
49                             if ((e = e.next) == null) {
50                                 if ((val = mappingFunction.apply(key)) != null) {
51                                     added = true;
52                                     pred.next = new Node<K,V>(h, key, val, null);
53                                 }
54                                 break;
55                             }
56                         }
57                     }
58                     else if (f instanceof TreeBin) {
59                         binCount = 2;
60                         TreeBin<K,V> t = (TreeBin<K,V>)f;
61                         TreeNode<K,V> r, p;
62                         if ((r = t.root) != null &&
63                             (p = r.findTreeNode(h, key, null)) != null)
64                             val = p.val;
65                         else if ((val = mappingFunction.apply(key)) != null) {
66                             added = true;
67                             t.putTreeVal(h, key, val);
68                         }
69                     }
70                 }
71             }
72             if (binCount != 0) {
73                 if (binCount >= TREEIFY_THRESHOLD)
74                     treeifyBin(tab, i);
75                 if (!added)
76                     return val;
77                 break;
78             }
79         }
80     }
81     if (val != null)
82         // 計數統計&閾值判斷+擴容操作
83         addCount(1L, binCount);
84     return val;
85 }

 

推薦閱讀:

更多文章可掃描以下二維碼:

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文主要是詳細介紹如何利用maven對SSM框架整合的思路及其詳解 ...
  • 現在我們已經能愉快地看著一頁一頁羅列出來的菜單進行點菜了。現在又有的小伙伴希望能夠加上一份餐後甜點的“子菜單”。怎麼辦呢?我們不僅僅要支持多個菜單,甚至還要支持菜單中的菜單。 如果我們能讓甜點菜單變成餐廳菜單集合的一個元素,那該有多好。但是根據現在的實現,根本做不到呀。我們想要的是這樣的: 我們需要 ...
  • 現在使用的仍是AWT的事件模型。涉及到3類對象: Event Source:事件源,即事件發生所在的組件 Event:事件,封裝了此次事件的相關信息 Event Listener:事件監聽器,監聽事件,發生指定事件時自動調用對應的方法 監聽器可以繼承介面自己寫代碼實現,也可以繼承適配器(空實現),然 ...
  • 首先python是一種面向對象、開源的、具有解析性的編程語言.接下來我們來看看python的基本一些語法. 學習一門語言,第一個程式就是Hello World程式.在Python裡面,Hello World的程式代碼如下: print("Hello World") 從這句代碼我們可以看出來,pyth ...
  • Spring Event 是基於觀察者模式實現,介紹其之前,我們先介紹下JDK提供的觀察者模型 觀察者:Observer, 被觀察:Observable 當被觀察者改變時,其需要通知所有關聯的觀察者。Observable實現邏輯如下: 好了,下麵我們介紹Spring基於觀察者模式的Event機制 首 ...
  • 一、SpringDataJpa的含義: SpringDataJpa: 是Spring基於ORM框架、JPA規範封裝的一套JPA應用框架,是SpringData中的一個子模塊,可讓開發者用極簡的代碼即可實現對數據的訪問和操作。它提供了包括增刪改查、排序、分頁等在內的常用功能,主要針對的就是 Sprin ...
  • 本文約3萬餘字,閱讀時間大概為1小時。主要包含:裸辭&辭職、西安&杭州、面試的技巧、螞蟻金服面試經歷、面試題分享等章節。距離來杭州已經過去3個月了,一直想把這幾個月的經歷寫下來,但是遲遲沒有動手,在整理了好久之後終於動手完成了本文,希望大家通過本文可以有所感悟。由於個人的工作經驗和視野有限,文章中的 ...
  • conda和pip簡介 conda conda是包及其依賴項和環境的管理工具。 適用語言:Python, R, Ruby, Lua, Scala, Java, JavaScript, C/C++, FORTRAN。 適用平臺:Windows, macOS, Linux 用途: 如果你需要的包要求不同 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...