连载中 6/20

锁竞争与等待唤醒:一百个线程抢一把锁

2026-06-27 · 6085 阅读 · 0 评论 · 0 赞

九十九个线程的游行示威

大促零点,会员卡 8801 的余额刚好够买限量套餐,一百个请求同时打进来,都要抢 lock:card:8801。一个抢到,进临界区干活,假设活要干 200 毫秒。剩下九十九个线程在干嘛?最朴素的实现是 while 循环:

// 反面教材:裸自旋
while (!tryLock()) {
    // 什么都不干,立刻再试
}

九十九个线程以最大频率疯狂 SET,200 毫秒内打出几十万次抢锁请求——抢锁的流量比业务本身大一千倍,Redis 的 QPS 被无效重试打满,别的业务跟着遭殃。这就是锁竞争的第一课:等待的姿势,比加锁本身更影响系统健康

第一档:有纪律的重试

最低成本的改良是给自旋上纪律:加随机退避(失败后睡随机毫秒再试),限制总等待时长(tryLock 的 waitTime 参数),到点放弃走降级。Redisson 的 tryLock(2, 30, TimeUnit.SECONDS) 就是这个语义:最多等 2 秒,抢不到别杵着。退避把九十九路持续轰击变成稀疏试探,Redis 压力立减。但它有个天花板:重试的粒度靠猜——睡 50 毫秒,可能锁 100 毫秒前就释放了,白等;睡 200 毫秒,可能锁刚释放就被人抢走,错过。想不猜,就得让「锁释放」这个事件主动通知等待者——等待唤醒登场。

第二档:pub/sub 唤醒

Redisson 的做法:每个锁名对应一个 pub/sub 频道,抢锁失败的线程订阅这个频道然后挂起(不占 CPU);持锁线程 unlock 时,Lua 里顺手向频道 publish 一条「解锁了」;等待者被唤醒,重新参与抢锁。这就是 lock.lock() 底层的等待机制(tryLock 则用信号量加超时实现)。相比自旋,锁释放到有人知晓,从「下一次轮询」缩短到「毫秒级推送」,Redis 也不用承受无效轰炸。

但它有两个诚实的缺陷。其一,pub/sub 是即发即弃:连接抖动错过那条 publish,等待者就睡着了——所以唤醒必须配超时兜底,超时后主动重试一轮,不能纯靠通知。其二,广播不是点播:解锁唤醒的是频道里所有等待者,九十九个一起惊醒、一起扑向锁,九十八个再次失败——这叫惊群。惊群多发生在竞争激烈的锁上,而激烈的锁本身就该反思设计。

第三档:公平锁与第四档:单飞

Redisson 的 FairLock 用一个 Redis list 给请求排队:抢锁先入队,队头才有资格尝试加锁,unlock 时摘掉队头并唤醒下一个——先到先得,无惊群,天然防饿死。代价是每次加锁多一串 list 操作(入队、看队头、比对自己的位置),吞吐明显低于非公平锁,竞争越激烈衰减越狠。JVM 内 ReentrantLock 公平模式的教训在分布式下原样重演:公平是有价的,默认别开。

更高一档的解法跳出锁本身——一百个线程抢一把锁,多数时候抢的是同一份结果,那就让一个人干活,大家等结果。这就是单飞(singleflight)模式:第一个请求抢锁、干活、把结果写进 Redis(带短 TTL);其余请求抢锁失败后不再执着于锁,先查结果 key,有结果直接拿走。缓存回源防击穿正是这个套路(Redis 系列第 5 篇),把「九十九次重活」收敛成「一次重活加九十九次读」,锁的竞争从根上消失。

等待策略Redis 压力响应延迟适用
裸自旋爆炸,禁用最短反面教材
退避 + 超时取决于重试间隔竞争温和,抢不到可放弃
pub/sub 唤醒毫秒级通用默认(Redisson lock 底层)
公平排队按序等待,无饿死必须按序执行的少数场景
单飞收敛极低第一个干活的慢,其余近零抢同一份结果的读场景

老王的领悟很到位:「一百个人抢一把锁,先想想是不是本来就不该一百个人都来抢。」锁是排他工具,能用共享结果收敛的竞争,别用互斥硬扛。下一篇回到安全性主线,讲 Redis 锁最被诟病的软肋——主从切换那一瞬间,锁会凭空消失。

503

10 年全栈工程师 · 503咖啡馆主理人

#分布式锁#自旋#等待唤醒#pubsub#单飞#公平锁

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 4 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞