一個使用本地緩存引起的線程阻塞問題

来源:http://www.cnblogs.com/jenwang/archive/2017/02/07/yi-ge-shi-yong-ben-de-huan-cun-yin-qi-de-xian-chen.html
-Advertisement-
Play Games

現象 有同事的java系統運行一段時間後發生請求阻塞的情況(返回504),從僅有的記憶體dump文件看,大部分線程都阻塞在了一個本地緩存(jodd cache)的讀鎖上了(ReentrantReadWriteLock$ReadLock.lock)。 排查過程 階段一 本能的反應應該是寫鎖被占用了才會出 ...


現象

有同事的java系統運行一段時間後發生請求阻塞的情況(返回504),從僅有的記憶體dump文件看,大部分線程都阻塞在了一個本地緩存(jodd cache)的讀鎖上了(ReentrantReadWriteLock$ReadLock.lock)。

排查過程

階段一

本能的反應應該是寫鎖被占用了才會出現這個情況。於是開始以"WriteLock.lock"為關鍵字搜索寫鎖,怎麼也搜不到。其實搜不到是正常的,因為寫鎖已經被占有了,當然不可能停在WriteLock.lock上了。

開始翻jodd LRUCache代碼,發現是用LinkedHashMap實現的,在dump文件上搜索LinkedHashMap寫操作的代碼,果然發現有一個線程是正在執行LRUCache的put方法,代碼停留在LRUCache的pruneCache方法中(就是在put的時候cache滿了回收一些位置):

protected int pruneCache() {
    if (isPruneExpiredActive() == false) {
        return 0;
    }
    int count = 0;
    //cacheMap就是一個LinkedHashMap的實例
    Iterator<CacheObject<K,V>> values = cacheMap.values().iterator();
    while (values.hasNext()) {
        CacheObject<K,V> co = values.next();
        if (co.isExpired() == true) {
            values.remove();
            count++;
        }
    }
    return count;
}
    

到這裡就證明瞭最初的猜想是對的,寫鎖被占了才導致那麼多讀線程被堵住。

可以看出 jodd 使用 LinkedHashMap + ReentrantReadWriteLock 實現LRUCache是有性能問題的,一個寫操作會鎖住整個緩存,阻塞所有讀操作。這是第一個問題

階段二

顯然不能到此就結束了,要有更高的追求,繼續分析LRUCache的具體實現,主要邏輯就是put時加上寫鎖,get時加上讀鎖,內部是一個開啟了accessOrder的LinkedHashMap作為數據存儲。

初看也貌似很正常沒啥問題啊。其實開啟了accessOrder的LinkedHashMap 多線程get是會有併發問題的,因為會把get到的元素移到雙向鏈表最前面,看LinkedHashMap的get方法:

public V get(Object key) {
    Entry<K,V> e = (Entry<K,V>)getEntry(key);
    if (e == null)
        return null;
    e.recordAccess(this);
    return e.value;
} 

void recordAccess(HashMap<K,V> m) {
    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
    if (lm.accessOrder) {
        lm.modCount++;
        remove();
        addBefore(lm.header);
    }
}

可以看到這裡改變鏈表結構是沒有任何併發控制的,因此LinkedHashMap併發get是不OK的,jodd給get加了讀鎖是存在併發問題的(還不明白的請自行學習ReentrantReadWriteLock機制)。這是第二個問題

可以想象下高併發時鏈表被破壞成各種奇形怪狀的情況(比較費腦力,我就不描述了),完全有可能讓上面pruneCache()方法中的values.hasNext()永遠為true。這次剛好是停在LRUCache#pruneCache中,下次就有可能停在LinkedHashMap#transfer上,一旦寫鎖裡面的代碼塊hang住,所有讀線程全部堵住,而且這種問題出現幾率不等,很難模擬重現。

JUC Bug

另外順便提一下某些早期JDK版本中存在的BUG

ReentrantReadWriteLock可能在沒有任何線程持有鎖的情況下被hang住:
http://bugs.sun.com/view_bug.do?bug_id=6822370
http://bugs.sun.com/view_bug.do?bug_id=6903249

小結

  • 不要使用Jodd的cache
  • 推薦使用gauva的cache
    基於concurrentlinkedhashmap實現,現已整合到guava里了
  • 不可輕信開源組件,使用前一定要先研究透徹

更多內容首發在 http://jenwang.me

進一步交流:

- Email:[email protected]

- 對於本博客某些話題感興趣,希望進一步交流的,請加 qq 群:2825967

- 更多技術交流分享在圈子「架構雜談」,跟老司機們聊聊互聯網前沿技術、架構、工具、解決方案等


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

-Advertisement-
Play Games
更多相關文章
  • 原文:http://blog.csdn.net/ysughw/article/details/8992322 ContextLoaderListener監聽器的作用就是啟動Web容器時,自動裝配ApplicationContext的配置信息。因為它實現了ServletContextListener這 ...
  • springmvc在處理器方法中通常返回的是邏輯視圖,如何定位到真正的頁面,就需要通過視圖解析器。 springmvc里提供了多個視圖解析器,InternalResourceViewResolver就是其中之一: 最常用的視圖解析器:InternalResourceViewResolver 當處理器 ...
  • xmlns是XML Namespaces的縮寫,中文名稱是XML(標準通用標記語言的子集)命名空間。 產生原因 比如: 下麵這個 XML 文檔攜帶著某個表格中的信息: table tr tdApples/td tdBananas/td /tr /table 下麵這個 XML 文檔攜帶有關桌子的信息( ...
  • 非常慚愧的說,由於之前一直使用的是windowservice,安裝apache來進行伺服器佈置的,這種方式也是最簡單最直接的方式, 但是由於php的服務大多都是linux棧的,咱們也不能落後呀,在寫了php半年之後,開始擁有的自己的第一臺centos的伺服器, 實話說,centos真的是簡潔,部署完 ...
  • 一、指針的四個關鍵概念1、指針的類型2、指針指向的類型3、指針的值,也就是指針指向的地址4、指針自己所占用的記憶體空間註意:指針變數所存的內容就是記憶體的地址編號!例如:int **pp = NULL;1、指針的類型是 int **2、指針指向的類型 int *3、指針的值為NULL4、指針自己所站記憶體 ...
  • 一:語法和語句 # :註釋 \ : 轉譯回車,繼續上一行,在一行語句較長的情況下可以使用其來切分成多行,因其可讀性差所以不建議使用 ; : 將兩個語句連接到一行,可讀性差,不建議使用 : : 將代碼的頭和體分開 語句(代碼塊)用縮進方式體現不同的代碼級別,建議採用4個空格(不要使用tab) pyth ...
  • [TOC] 2.2.7 新的型別轉換操作符(Type Conversion Operators) 1. static_cast 將一個值以符合邏輯的方式轉型。這可看做是“利用原值重建一個臨時對象,併在設立初值時使用型別轉換”。 3. const_cast 設定或去除型別的常數性,亦可去除volati ...
  • 原文 http://www.cnblogs.com/selene/p/4607004.html 一:SqlMapConfig.xml配置文件的內容和配置順序如下 二:properties屬性 作用:將數據連接單獨配置在db.properties中,只需要在SqlMapConfig.xml中載入db. ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...