現象 有同事的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
- 更多技術交流分享在圈子「架構雜談」,跟老司機們聊聊互聯網前沿技術、架構、工具、解決方案等