居然還有比 ReadWriteLock 更強(qiáng)的鎖:StampedLock
居然还有比 ReadWriteLock 更强的锁:StampedLock
有一天,手上维护的服务开始神秘性卡顿,大家掰着手指头数着是不是垃圾回收、数据库慢、线程池爆炸。结果扒着线程 dump 仔细一看,主线程正一脸懵逼地挂在了 ReadWriteLock 上。老哥,这玩意儿不是号称并发读写神器么?咋还成了绊脚石?
看着代码里那段熟悉的套路:
lock.readLock().lock();
try {
// 一些业务逻辑
} finally {
lock.readLock().unlock();
}
说实话,这种模式真是用了八百回。可是,问题就卡在这里:读多写少的局面下,依然有些线程等得脖子快长断了。就如排队买奶茶,好家伙,等了半天队伍没动,后面的人都快打起来了!
谁是强化锁 StampedLock?
正郁闷呢,隔壁“懂王”同事轻飘飘来了一句:“StampedLock不用试试?”我一愣,这啥玩意儿,听名字像是邮票一样粘在锁头上,能寄快递还是咋的?
查了查文档,发现 StampedLock 其实算是 Java 8 新加的黑科技。它号称支持“三段变形”:
- 悄咪咪读:乐观读锁 tryOptimisticRead
- 传统读锁:悲观读锁 readLock
- 写操作:写锁 writeLock
牛逼吧?仿佛攒了三种超能力在一起。最关键,这玩意竟然还能避免读锁之间的阻塞,而且是靠“邮票”(stamp)来标识自己拿到的锁,时间戳都搬出来了。这要给 ReadWriteLock 打几分?起码星星多三颗吧。
踩坑瞬间
其实刚一看 StampedLock 的用法,那叫一个心跳加速。尤其是乐观读锁,看了官方 demo,满脑袋问号:
long stamp = lock.tryOptimisticRead();
int value = data;
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
value = data;
} finally {
lock.unlockRead(stamp);
}
}
第一次用,我可开心了,写完就上生产(真不怕打死)。结果线上直接给我来了个“值读乱了”。隔壁业务同学都来找我聊天了,说“怎么你们缓存突然全是旧数据造的吗?”
原因其实简单粗暴——乐观读锁就是乐观,有写并发时 stamp 会失效,你得乖乖回到读锁再重新读一遍。可我一开始偷懒,validate 失败居然没管它还继续用脏数据……直接翻车。
后来又遇到个操作,unlock 忘了加 stamp,愣给 unlock 死在源码崖底下,找了十分钟才发现 unlockRead 需要传 stamp,这和 ReadWriteLock 可不一样,它不像 unlock() 那么好用。
经验启示
过来人的血泪经验如下总结,欢迎围观甚至拍砖:
- 乐观读?用,但别懒。validate stamp 失败,记得老老实实重来一遍,别想着“应该没事吧”。
- 写锁期间,所有乐观读都会被标记失效,所以对一致性要求真的高的时候,你得选悲观读。
- “邮票”这东西,千万别乱搞。每个加锁方法返回的 stamp 必须匹配 unlock,否则有你好果汁喝。
- 组合锁可玩性高,但不要随便组合,否则到时候 Deadlock 啦啦队直接喊进你家门。
StampedLock 给我带来的体感是:
| 优点 | 坑点/注意事项 |
|---|---|
| 提升读性能 | 乐观锁需二次校验 |
| API 灵活 | unlock 得要 stamp 参数 |
| 支持写转读等骚操作 | 出错时 bug 难排查 |
最后一个想吐槽点:StampedLock 其实是不可重入的(你 lock 两次会把自己锁死),而且还不支持条件变量,有点反人类。用之前三思,别拿正经业务现场试刀。
好啦,今天的邮票锁故事说完了——Java 永远有你不熟的小秘密,还得自己多踩踩坑头发才掉得够快。不说了,下次再聊,那会儿估计又冒出个什么“宇宙锁”了。
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章