文档章节

一个使用本地缓存引起的线程阻塞问题

j
 jenwang
发布于 2017/02/07 11:40
字数 875
阅读 7
收藏 0

现象

有同事的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:jenwang@foxmail.com

- 对于本博客某些话题感兴趣,希望进一步交流的,请加 qq 群:2825967

- 更多技术交流分享在圈子「架构杂谈」,跟老司机们聊聊互联网前沿技术、架构、工具、解决方案等

© 著作权归作者所有

j
粉丝 0
博文 15
码字总数 8845
作品 0
杭州
CTO(技术副总裁)
私信 提问
多线程之:java的CAS操作的相关信息

一:锁机制存在的性能问题? 在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁(后面的章节还会谈到锁)。 锁机制存在以下问题: (1)在多线程竞争下,加锁、释放锁会导...

无信不立
2016/07/20
0
0
缓存写法总结

基本写法 为了方便演示,这里使用Runtime.Cache做缓存容器,并定义个简单操作类。如下: 简单读取: 在项目中,有不少这样写法,这样写并没有错,但在并发量上来后就容易出问题。 缓存雪崩 ...

吞吞吐吐的
2017/09/26
0
0
如何减少接口响应时间

如何减少接口响应时间 Premature optimization is the root of all evil.   — Donald Knuth 对于程序优化,我一直采取保守的态度,除非万不得已。但是随着业务的不断发展,程序越来越复杂,...

nj-zhangmq
2016/12/21
10
0
线程基础:多任务处理(15)——Fork/Join框架(要点2)

========(接上文《线程基础:多任务处理(14)——Fork/Join框架(要点1)》) 2-3. ForkJoinPool中的队列 那么ForkJoinPool是怎样创建队列的呢?请看如下两段源代码片段: externalPush方法...

yinwenjie
2017/05/31
0
0
自旋锁学习系列(2):TAS锁

TAS 是test and set 的缩写,直白的翻译过来就是比较然后测试。java中的原子类大量使用了TAS操作。通过TAS 我们可以安全并且无阻塞的设置原子变量,不用加锁也能进行线程安全的操作。本文目的...

凯奥斯
2013/06/25
855
1

没有更多内容

加载失败,请刷新页面

加载更多

rime设置为默认简体

转载 https://github.com/ModerRAS/ModerRAS.github.io/blob/master/_posts/2018-11-07-rime%E8%AE%BE%E7%BD%AE%E4%B8%BA%E9%BB%98%E8%AE%A4%E7%AE%80%E4%BD%93.md 写在开始 我的Arch Linux上......

zhenruyan
今天
5
0
简述TCP的流量控制与拥塞控制

1. TCP流量控制 流量控制就是让发送方的发送速率不要太快,要让接收方来的及接收。 原理是通过确认报文中窗口字段来控制发送方的发送速率,发送方的发送窗口大小不能超过接收方给出窗口大小。...

鏡花水月
今天
9
0
OSChina 周日乱弹 —— 别问,问就是没空

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @tom_tdhzz :#今日歌曲推荐# 分享容祖儿/彭羚的单曲《心淡》: 《心淡》- 容祖儿/彭羚 手机党少年们想听歌,请使劲儿戳(这里) @wqp0010 :周...

小小编辑
今天
919
11
golang微服务框架go-micro 入门笔记2.1 micro工具之micro api

micro api micro 功能非常强大,本文将详细阐述micro api 命令行的功能 重要的事情说3次 本文全部代码https://idea.techidea8.com/open/idea.shtml?id=6 本文全部代码https://idea.techidea8....

非正式解决方案
今天
5
0
Spring Context 你真的懂了吗

今天介绍一下大家常见的一个单词 context 应该怎么去理解,正确的理解它有助于我们学习 spring 以及计算机系统中的其他知识。 1. context 是什么 我们经常在编程中见到 context 这个单词,当...

Java知其所以然
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部