1. 没有锁的时候,程序到底会发生什么——从一次脏读事故说起
先别急着看互斥锁的定义。我讲一件亲身经历的事。
前几年我们维护一个订单服务,核心逻辑不复杂:用户下单后,要在一张预存余额表上做“余额扣减并检查余额是否够用”的操作。代码写得很直白。
java复制if (account.balance >= order.amount) {
account.balance -= order.amount;
accountRepository.save(account);
return "下单成功";
} else {
return "余额不足";
}
单机、单线程联调,跑了十几次全过。结果一上线,在双十一预热的流量下,用户不断反馈:余额只够买一件商品的钱,却连续下单成功了三四个订单。财务对账时发现,余额被扣成了负数。
问题出在哪?两个请求同时读到 account.balance = 100,都发现大于订单金额 50,于是都执行了“扣减并保存”,后保存的覆盖了先保存的,最终余额显示为 50——但实际应该已经被扣到 0。两个订单都成功了,金额只扣了一次。
这就是典型的并发竞争条件(Race Condition)。它不一定会出问题,但一旦流量撞上来,结果通常是灾难性的。
我用生活里的例子帮你建立直觉:
想象家里只有一个卫生间(共享资源),你和室友(多个线程)都要用。如果没有任何规则,就会出现两个人都推门进去的情况。互斥锁就是门口那把“只能被一个人从里面反锁、其他人必须排队等待”的门锁。
互斥锁(Mutual Exclusion Lock)解决的就是这个问题:在任意时刻,只允许一个线程进入被保护的代码区域(我们把它叫做“临界区”),其他线程想在同时进入,就必须先等在门外,直到里面的线程离开并释放锁。
它是并发编程中最基础、也被使用得最广泛的同步原语。无论是 Java 的 synchronized 与 ReentrantLock、C++ 的 std::mutex、Python 的 threading.Lock,还是数据库的行锁、Redis 的 SETNX 分布式锁,本质上都是互斥思想在不同层级上的实现。
这篇文章我不会只给你列 API 文档。我会从“为什么需要它”讲到“它在操作系统层面到底做了什么”,再把使用中高频踩坑的死锁、性能问题、锁粒度错位这些事一次性讲透。适合已经写过一点并发代码、但总感觉“调不对”的开发者阅读。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 互斥锁的最小工作单元:从硬件原子指令看锁的本质
说到锁,很多教程会直接跳到“加锁 lock() / 解锁 unlock()”的 API 调用。但如果你不理解锁的一层核心机制——原子性(Atomicity),后面遇到棘
