自旋锁到底在“转”什么?从抢锁到 CPU 空转,讲透原理与适用场景

发布时间:2026/10/21 3:04:40

自旋锁到底在“转”什么?从抢锁到 CPU 空转,讲透原理与适用场景 平时说到锁很多人脑子里第一反应都是没抢到锁那就等着。但自旋锁不一样。它的思路有点“轴”锁现在被别人拿着没关系我不睡我就在这儿一直试直到你把锁放出来。这就是“自旋”两个字的由来。听起来似乎很浪费 CPU但它偏偏又大量存在于操作系统、JVM、并发框架和底层组件里。原因也不复杂有时候等一会儿比睡一觉再醒来更划算。一、先用一个很简单的例子理解自旋假设公司只有一台打印机。小王正在打印一张纸你也要打印。这时候有两种做法。第一种发现打印机被占用 ↓ 回工位坐下 ↓ 等别人通知 ↓ 再走回来打印这很像普通的阻塞等待。另一种打印机被占用 ↓ 站旁边等几秒 ↓ 看看好了没 ↓ 没好再看一次 ↓ 好了马上接着用这就是自旋锁的感觉。区别就在于阻塞锁会让当前线程睡眠自旋锁不会它会一直尝试拿锁。二、自旋锁底层到底在做什么最简单的自旋锁可以先写成这种伪代码while (lock 1) { // 什么都不干一直检查 } lock 1; // 临界区代码 lock 0;意思非常直接lock 0代表锁空闲。lock 1代表锁已经被占用。线程来抢锁时如果发现lock 1就一直循环。这就是所谓busy waiting忙等。不过上面这段代码实际上是不安全的。因为两个线程可能同时看到lock 0然后一起执行lock 1这样两个人就同时进临界区了锁直接失效。所以真正的自旋锁必须依赖一种非常重要的 CPU 原子操作CAS也就是Compare And Swap比较并交换。三、CAS 才是自旋锁真正的底座CAS 可以简单理解成一句话如果这个值现在还是我预期的值就把它改掉如果已经被别人改了那我什么都不做。比如当前 lock 0线程 A 想拿锁。它执行CAS(lock, 0, 1)意思是如果 lock 现在还是 0 就改成 1如果成功线程A拿到锁如果失败说明别人先拿走了于是继续尝试。代码大概可以理解成while (!CAS(lock, 0, 1)) { // 自旋 }释放锁lock 0;所以一个最简化的自旋锁本质其实就是CAS ↓ 失败 ↓ 继续 CAS ↓ 失败 ↓ 继续 CAS ↓ 成功 ↓ 进入临界区这就是所谓的“自旋”。四、为什么不直接让线程睡觉问题就来了。既然没抢到锁那把线程挂起不就好了为什么还要浪费 CPU 一直循环关键在于线程睡眠和唤醒其实并不是免费的。假设线程 A 没拿到锁。如果使用阻塞锁可能经历线程A运行 ↓ 发现锁被占用 ↓ 线程进入阻塞状态 ↓ 操作系统调度其他线程 ↓ 保存线程A上下文 ↓ 过一会锁释放 ↓ 重新唤醒线程A ↓ 恢复线程A上下文 ↓ 继续运行这里涉及一个重要概念上下文切换。CPU 要保存和恢复寄存器程序计数器栈信息线程状态这些操作都有成本。如果对方只占锁几百纳秒或者几微秒那你刚把线程挂起来锁可能已经释放了。结果变成为了等一会儿反而付出了一次完整的线程切换成本。这就像你去厕所发现有人“里面有人。”如果对方还有 5 秒就出来你没必要坐电梯回办公室 ↓ 坐下 ↓ 再坐电梯回来站门口等一下反而更快。这正是自旋锁存在的意义。五、自旋锁真正赌的是什么自旋锁其实是在赌一件事我赌你很快就会把锁释放。如果赌对了线程A持锁 ↓ 线程B自旋几次 ↓ 线程A释放 ↓ 线程B立刻拿到非常划算。因为线程 B 根本没有发生阻塞和唤醒。但如果赌错了线程A持锁10秒线程 B 还在那里CAS CAS CAS CAS CAS CAS ……那就惨了。CPU 可能被它白白占满。所以自旋锁有一个非常明显的特点适合锁持有时间非常短的场景。六、为什么自旋锁特别适合多核 CPU假设只有一个 CPU 核心。线程 A 拿着锁A持有锁线程 B 开始自旋B疯狂检查锁问题来了。CPU 此时正在执行 B。那 A 怎么运行A 不运行就没办法释放锁。于是很可能出现B在等A释放锁 但B一直占着CPU A反而得不到运行机会所以在单核环境里自旋通常意义不大。但多核就不一样。比如CPU Core 1 → 线程A → 正在使用锁 CPU Core 2 → 线程B → 正在自旋这时候A继续执行 ↓ 很快释放锁与此同时B一直观察 ↓ 立刻拿到锁这才是自旋锁最理想的状态。所以自旋锁天然和多核 CPU关系非常密切。七、自旋锁最大的缺点它真的会烧 CPU假设线程A拿锁以后 执行了5秒线程 Bwhile (!CAS(...)) { }这 5 秒里它并不是睡着。而是在判断 失败 判断 失败 判断 失败 ……CPU 使用率可能直接上去。如果只有一个线程还好。如果100个线程同时竞争一把锁线程1 → 自旋 线程2 → 自旋 线程3 → 自旋 …… 线程100 → 自旋所有人一起抢 抢 抢 抢 抢CPU 就会花大量时间在无意义竞争上。所以自旋锁不是“比阻塞锁高级”而是适用于不同场景。八、实际系统一般不会让线程无限自旋工程里通常不会真的写while (true) { CAS... }然后一直转到天荒地老。更常见的策略是先自旋 ↓ 尝试几次 ↓ 还是失败 ↓ 再阻塞比如尝试1次 失败 尝试2次 失败 尝试3次 失败 ……达到一定次数以后算了不等了 ↓ 线程挂起这样就把两种方案结合起来了短时间等待 → 自旋 长时间等待 → 阻塞这其实比“永远自旋”聪明得多。九、自旋次数应该固定吗也不一定。更聪明的实现甚至会根据之前的情况调整。比如某个锁以前经常自旋3次左右就拿到了那系统可能觉得这个锁一般释放得挺快可以多等一会儿。但如果过去经常自旋半天都拿不到系统可能就会别浪费 CPU 了早点挂起吧。这种思路通常叫自适应自旋。核心意思就是自旋多久不一定固定而是根据运行情况动态决定。十、自旋锁和互斥锁到底怎么选可以把它们想成两个完全不同的等待策略。自旋锁没抢到我就在门口等特点不睡眠 不主动让出 CPU 响应快 但浪费 CPU适合临界区非常短 锁很快释放 竞争不是特别激烈 多核 CPU阻塞锁没抢到我先回去睡一会 有位置再叫我特点节省 CPU 但涉及阻塞和唤醒 可能有上下文切换成本适合锁可能持有较长时间 竞争激烈 等待时间不可预测所以两者并不存在谁绝对更快。真正决定性能的是等待时间 竞争程度 CPU核心数量 临界区长度十一、Java 里其实到处都有“自旋思想”Java 开发里可能很少自己手写一个SpinLock但自旋思想其实非常常见。比如AtomicInteger AtomicLong AtomicReference这些原子类大量依赖CAS典型逻辑就是读取旧值 ↓ 计算新值 ↓ CAS尝试更新 ↓ 失败 ↓ 重新读取 ↓ 再次CAS本质上就是一种CAS 重试。再比如很多高性能并发组件也会使用类似思路先快速尝试 ↓ 短暂自旋 ↓ 竞争严重后再阻塞因为真正高性能的并发设计通常不是简单选择一直自旋或者直接阻塞而是在两者之间找平衡。十二、还有一个细节自旋时最好别真的“死转”最粗暴的写法while (locked) { }虽然能转但并不友好。现代 CPU 和运行时通常会提供一些“提示”。Java 里有Thread.onSpinWait();它相当于告诉 CPU我现在正在自旋等待不是在做正常计算。底层可以利用类似PAUSE这样的 CPU 指令优化自旋行为。好处包括减少资源消耗 降低流水线压力 改善超线程环境表现所以真正的高性能自旋并不是傻乎乎地疯狂 while。而是尽量让 CPU 知道这个线程只是在短暂等待。十三、一个例子把自旋锁彻底串起来假设现在有两个线程。线程 A拿到锁 ↓ 修改一个计数器 ↓ 只需要几十纳秒 ↓ 释放锁线程 B 此时过来。如果采用阻塞发现锁被占 ↓ 线程挂起 ↓ 操作系统调度 ↓ A释放锁 ↓ 重新唤醒B ↓ B继续运行有可能线程切换的成本比真正等待锁的时间还大。换成自旋B第一次CAS → 失败 B第二次CAS → 失败 B第三次CAS → 成功整个过程可能很快就结束了。这里自旋非常划算。但如果 A 拿锁以后请求数据库 ↓ 调用远程接口 ↓ 睡眠3秒B还在那里自旋CAS CAS CAS CAS ……那就是典型的错误使用。所以判断自旋锁适不适合只需要问一个问题拿锁的人到底多久能出来写在最后自旋锁看起来像是一个很高级的并发概念其实它的思想特别直接锁暂时拿不到但我觉得你马上就会释放所以我先不睡原地再试几次。它的底层核心可以压缩成CAS ↓ 成功 → 拿锁 失败 ↓ 继续 CAS ↓ 继续尝试而它存在的真正原因是线程阻塞 线程唤醒 上下文切换本身也是有成本的。所以当等待锁的时间 线程挂起和重新调度的成本自旋往往更划算。但反过来如果锁长期不释放自旋 拿 CPU 时间不停做无用功这时候阻塞反而更合理。所以理解自旋锁以后会发现它背后其实是一种很典型的性能优化思想不要一发现资源不可用就立刻进入重量级等待而是先赌一次——它也许马上就好了。赌对了省掉一次昂贵的线程切换。赌错了就得及时收手。这也是为什么真正成熟的并发实现通常不是“只自旋”或者“只阻塞”而是在两者之间动态取舍。
延伸阅读

更多相关文章

2026/10/19 22:13:00

Excel行列求和:两组快捷键搞定全表与跳空汇总

​在日常处理Excel报表时,行列求和恐怕是出现频率最高的操作之一。很多人习惯用鼠标点击工具栏上的“自动求和”按钮,或者手工输入SUM函数再拖动填充,虽然也能得出结果,但效率总差那么点意思。其实,Excel内置了一组堪称…

2026/10/19 19:23:00

力扣高频SQL50题知识点

一、查询select product_id from Products where low_fatsY and recyclableY;select 输出 from 表名where 条件;select name from Customer where referee_id ! 2 or referee_id is NULL;对于条件为某一元素为NULL用“元素 is NULL”select name,population,area…

2026/10/21 2:40:48

day24_jsvmp后两个生成算法

一、在switch中插桩,先看大概日志信息有什么变化! “case”,a[F1],“stack”,Y看见array一直递增!数组变化【视频35分】 我们就知道大概要插桩什么了,比如chartCodeAt,String.FromChartCode,parseInt等等操…

2026/10/21 3:01:06

基于partial范式的.NET源码生成器开发与NuGet打包实战

干过这几年 .NET 项目,要说最值得投入的技术方向,源码生成器绝对排在我列表的前三。尤其是基于 partial 范式去设计生成逻辑,再把它打包成 NuGet 分发给团队使用,这一整套流程,做完一次以后基本就离不开了。很多人对源…

2026/10/21 3:01:06

Flutter for OpenHarmony:家具购买记录日历视图实战

最近在做 Flutter 跨端 APP 的时候,接了一个在 OpenHarmony 上跑家具购买记录的需求。功能不复杂,但做到日历视图那一层时,很多细节比预想中多。这篇就完整复盘一下这个“Flutter for OpenHarmony 家具购买记录App实战:日历视图实…

2026/10/21 3:01:06

SpringBoot+Vue在线商场后台管理系统:从源码拆解到部署上线

拿到“基于SpringBootVue的在线商场后台管理系统(源码lw部署文档讲解等)”这个标题,很多同学第一反应是“又是一个毕设模板”。但以我接手过不少类似模拟项目的经验来看,这类系统的价值恰恰不在于“能跑”,而在于它把前后端分离、权限控制、订…

2026/10/21 3:01:06

从零手写支持向量机:最大间隔、对偶与核技巧的工程实现

简介:这份资源是支持向量机(SVM)的Python实现代码包,面向正在学习机器学习算法、希望从零理解SVM原理并动手实践的学生与开发者。包内共6个文件,以3个Python脚本为核心,分别承担SVM主体实现、测试验证与数据…

2026/10/21 3:01:06

Flutter for OpenHarmony家具记录App:日历视图自绘与数据联动实践

做Flutter for OpenHarmony的家具购买记录App,是我最近一个月推进的一个业余项目。说实话,日历视图这种界面,在很多人的眼里就是“画个格子、放几个数字”,可真把它塞进一个需要实际记录购买数据的App里,涉及的日期计算…

2026/10/21 2:56:05

NVFP4预训练大模型:从量化训练到部署的全面解析

最近把这篇《Pretraining Large Language Models with NVFP4》翻来覆去读了几遍,越读越觉得它不只是“又一篇4位精度论文”,更像是给整个量化生态指了一个新方向。过去大家默认FP4只能做推理阶段的压缩,谁在预训练里碰4位精度,谁就…

2026/10/19 18:06:35

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/21 0:00:35

交通流仿真实战:IDM跟驰与MOBIL换道模型参数调优与避坑指南

简介:这份资源是面向交通工程学习者与Python开发者的微观交通流仿真项目,核心实现智能驾驶模型(IDM)下的车辆跟驰与换道行为,可用于理解速度、密度与流量之间的动态关系,适合具备Python基础、希望深入交通模…

2026/10/21 0:00:35

揭穿智能体表面顺从:用追踪代码重建任务执行确定性

1. 表面顺从不是智能体的“礼貌”,而是任务执行链路上的隐性断点最近在调试一个跨平台长周期任务调度系统时,我反复遇到一种令人困惑的现象:智能体在用户指令发出后,几乎秒级响应“已收到”“正在处理”“请稍候”,UI上…

2026/10/21 0:00:35

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑