真实面经题目 · 原创解析
锁的实现原理是什么?
锁的实现原理可以概括为:用一个可被原子修改的状态表示锁是否被占用,用所有者信息判断谁持有锁,用等待队列管理竞争失败的线程,用阻塞与唤醒降低空转成本,并通过内存屏障建立临界区内外的可见性与有序性保证。理解锁不能只停留在“加锁和解锁”两个动作,而要能讲清互斥、状态变更、线程挂起、唤醒、公平性、重入、超时、中断和竞争优化之间的关系。
真实面经题目 · 原创解析
锁的实现原理可以概括为:用一个可被原子修改的状态表示锁是否被占用,用所有者信息判断谁持有锁,用等待队列管理竞争失败的线程,用阻塞与唤醒降低空转成本,并通过内存屏障建立临界区内外的可见性与有序性保证。理解锁不能只停留在“加锁和解锁”两个动作,而要能讲清互斥、状态变更、线程挂起、唤醒、公平性、重入、超时、中断和竞争优化之间的关系。
锁本质上是对共享资源访问顺序的控制机制。它通常包含三类核心信息:锁状态、持有者和等待者。线程获取锁时,会先通过原子操作尝试把锁状态从未占用改为已占用;成功后成为持有者,进入临界区。失败时,根据锁的实现策略,线程可能自旋重试,也可能进入等待队列并被阻塞。释放锁时,持有者会恢复锁状态,并从等待队列中选择一个或多个线程唤醒。从底层看,锁依赖两个关键能力:第一是原子性,例如 CAS、原子交换或平台互斥原语,保证多个线程不能同时把锁状态改成功;第二是内存可见性,获取锁和释放锁通常会配合内存屏障,使释放前对共享变量的写入,对后续成功获取同一把锁的线程可见。在对象监视器模型里,锁和对象头、监视器、入口队列、等待队列相关;在 AQS 类模型里,锁主要由一个 volatile 状态字段、CAS 修改、独占线程记录和 CLH 变体等待队列组成。公平锁会尽量按排队顺序授予锁,减少饥饿但吞吐可能下降;非公平锁允许插队,吞吐通常更好但极端情况下可能导致等待时间不可控。
锁首先解决的是互斥问题:同一时刻只允许一个线程进入被保护的临界区。临界区不是代码块本身的魔法,而是一段对共享状态进行读取、修改或发布的区域。只要所有访问共享状态的路径都遵守同一把锁的约束,就能把并发访问串行化,避免读到中间状态或发生覆盖写。
锁实现通常会维护一个状态值,例如 0 表示未占用,1 表示已占用,或者用计数值表示重入层数。独占锁还会记录当前持有者,用来判断释放是否合法以及同一线程是否可以重入。可重入锁的关键不是“重复加锁不阻塞”这么简单,而是同一持有者再次获取时增加计数,释放时逐层递减,直到计数归零才真正让出锁。
多个线程同时争抢锁时,必须依赖原子操作决定唯一胜者。常见方式是 CAS:线程读取当前状态,期望它是未占用状态,然后尝试原子更新为已占用状态。只有一个线程能成功,其他线程会看到失败结果。CAS 解决的是状态修改不可被打断的问题,但不能单独解决长期竞争下的等待管理,所以还需要自旋、排队或阻塞机制配合。
竞争失败后,线程可以短暂自旋,反复尝试获取锁,适合锁持有时间很短的场景,因为避免了线程挂起和恢复的系统开销。但如果锁持有时间较长,自旋会浪费 CPU。更通用的做法是把线程放入等待队列,然后通过 park、系统调用或运行时调度机制挂起,等锁释放时再 unpark 或唤醒。高质量锁实现通常会在自旋和阻塞之间做权衡。
等待队列用于保存竞争失败的线程,并决定唤醒顺序。AQS 类实现通常把等待线程组织成双向队列,线程获取失败后入队,前驱节点释放锁或状态变化后,后继线程再尝试获取。等待队列让竞争从大量无序抢占变成有组织的排队,减少无效争抢,也为公平锁、可中断获取、超时获取等能力提供基础。
锁不仅提供互斥,还提供内存语义。释放锁前的写入,必须对之后成功获取同一把锁的线程可见;获取锁之后,线程不能继续使用过期缓存来判断共享状态。这通常通过 volatile 语义、内存屏障、monitor enter/exit 或运行时插入的屏障完成。没有可见性保证的“锁”只能限制执行顺序,不能可靠保护共享数据。
对象监视器模型中,获取锁和释放锁围绕监视器展开,涉及对象头中的标记信息、持有者、入口队列以及等待通知机制。AQS 模型则更偏框架化:用一个 volatile state 表示同步状态,用 CAS 修改 state,用 exclusiveOwnerThread 记录独占持有者,用队列保存等待线程。前者常见于内置监视器语义,后者常见于可重入独占锁、读写锁、信号量等同步器。
公平锁会先检查等待队列,尽量让排队更早的线程先获取锁,优点是等待时间更可控,缺点是上下文切换更多、吞吐可能下降。非公平锁允许新来的线程直接尝试抢锁,减少调度开销,吞吐常常更高,但可能出现某些线程长期等待。实际实现还会结合锁消除、锁粗化、偏向或轻量级策略、自适应自旋等优化,目标是在低竞争时尽量便宜,在高竞争时避免 CPU 浪费。
CAS 只能保证单次状态更新的原子性,不能天然表达复杂临界区、阻塞等待、重入、条件队列、超时和公平性。锁是在 CAS 等原子能力之上封装出的更完整同步机制,用来保护一段共享状态操作。
自旋锁在获取失败后继续占用 CPU 重试,适合临界区极短、竞争时间可预测的场景。阻塞锁会把线程挂起,减少 CPU 消耗,但挂起和唤醒存在调度成本。现代锁通常会结合短暂自旋和阻塞排队。
因为获取锁和释放锁会配合内存语义。释放锁时要把临界区内的写入对外发布,获取锁时要读取到释放方发布后的结果。这样同一把锁的释放与后续获取之间形成可见性保证。
不一定。公平锁能减少插队和饥饿风险,但会增加排队检查和线程切换成本,吞吐可能更低。非公平锁允许刚到的线程直接尝试获取,可能提升吞吐,但等待时间更不稳定。
可重入锁会记录持有线程和重入计数。持有线程再次获取同一把锁时,不会进入等待队列,而是增加计数;释放时递减计数,只有计数归零才清空持有者并唤醒后续等待线程。
monitor 更偏运行时内置的对象监视器机制,围绕对象锁、入口队列和等待通知工作。AQS 是构建同步器的框架化模型,核心是 state、CAS、持有者记录和等待队列,能扩展出独占锁、共享锁和多种同步组件。