Node.waitStatus直接影响线程是否立即抢锁:非公平锁跳过队列检查直接CAS抢占,公平锁需通过hasQueuedPredecessors()验证waitStatus有效性后才决定入队或放弃。
Node.waitStatus 怎么影响线程是否立即抢锁
公平锁和非公平锁在
中的关键分歧,就藏在
这个判断里——它本质是检查 AQS 同步队列头节点的后继是否为当前线程,也就是「队列里有没有人排在我前面」。这个检查依赖于 CLH 队列中每个
的
字段(如
、
、
)是否被正确设置。
非公平锁跳过这步:只要
,就直接
尝试抢占,哪怕队列里已有 5 个
处于
状态等待唤醒;而公平锁必须先调用
,该方法会读取头节点的
字段并判断其
是否有效——若队列不为空,就放弃 CAS,乖乖入队。
这意味着:非公平锁的线程在锁释放瞬间有两次“免排队”机会(
初始 CAS +
再次 CAS),而公平锁只有一次且受队列状态约束。
为什么非公平锁能减少 Node 入队频次
频繁入队意味着更多内存分配、CAS 更新
指针、以及后续可能的线程挂起(
)。这些操作都有开销,尤其在高竞争下会放大。
非公平锁下,一个刚释放锁的线程,很可能被下一个恰好执行
的线程直接抢到,根本不会走到
和
公平锁强制所有新请求线程都走完整入队流程:新建
→ CAS 设置为 tail → 设置前驱
→ 才进入 park
即使队列里只有 1 个等待者,公平锁也拒绝“插队”,导致本可复用的 CPU 时间片被浪费在上下文切换上
实测场景(100 线程争抢单许可锁)中,非公平锁平均入队率约 30%,公平锁接近 95%——差异直接反映在
实例数量和 GC 压力上。
公平锁的队列唤醒路径更长
公平锁要求「头节点释放后,只唤醒 successor」,这依赖
的
被准确维护。但唤醒不是原子动作:
需要遍历
链表跳过
节点,再 CAS 修改目标
的
为
,最后调用
。
X-Node企业快速建站1.0.6.0801
特色介绍: 1、ASP+XML+XSLT开发,代码、界面、样式全分离,可快速开发 2、支持语言包,支持多模板,ASP文件中无任何HTML or 中文 3、无限级分类,无限级菜单,自由排序 4、自定义版头(用于不规则页面) 5、自动查找无用的上传文件与空目录,并有回收站,可删除、还原、永久删除 6、增强的Cache管理,可单独管理单个Cache 7、以内存和XML做为Cache,兼顾性能与消耗 8、
下载
而非公平锁在
后,如果发现
又被新线程通过初始 CAS 抢走,就完全绕过队列唤醒逻辑——连读
都省了。
关键点:
的
不是单纯标记“等待中”,而是参与调度决策的状态机
公平性越强,对
链表结构和
一致性的依赖越重
一旦发生虚假唤醒或
节点堆积(比如超时 acquire 失败),公平锁的唤醒延迟会明显升高
实际选型时最容易忽略的细节
很多人以为「设成公平锁就能避免饥饿」,但现实是:如果业务本身存在长耗时临界区(比如锁内做 RPC 调用),哪怕用公平锁,后到的线程依然会在队列里等几十毫秒——此时吞吐量下降和响应毛刺并存,反而不如非公平锁配合合理超时控制来得稳定。
真正需要公平锁的场景极少,典型如:资源配额系统(必须严格按申请顺序扣减)、测试环境复现竞态、或监管合规要求「先到先服务」。其余绝大多数 Web/微服务场景,默认非公平锁 +
是更务实的选择。
tryAcquirehasQueuedPredecessors()NodewaitStatus0SIGNALCANCELLEDstate == 0compareAndSetState(0, 1)NodeSIGNALhasQueuedPredecessors()nextwaitStatuslock()tryAcquiretailLockSupport.park()lock()addWaiter()acquireQueued()NodewaitStatus = SIGNALNodeNodewaitStatusunparkSuccessor()nextCANCELLEDNodewaitStatus0LockSupport.unpark()release()statehead.nextNodewaitStatusNodewaitStatusCANCELLEDtryLock(timeout)