synchronized锁的原理?

1 个回答

  • 龙傲天
    我一路种下蘑菇,只为让你知道回家的路。
    # 1) 它到底锁了谁? * `synchronized(obj){…}` 与 `synchronized` 方法本质都是对\*\*某个对象的监视器(monitor)\*\*加锁。 * 实现形态: * 代码块:字节码 `monitorenter` / `monitorexit`。 * 方法:方法标志位 `ACC_SYNCHRONIZED`,JVM 在方法进入/退出处隐式插 `monitorenter/monitorexit`。 * `static synchronized` 锁的是 `Class` 对象;实例方法锁的是**当前实例**。 # 2) 对象头与锁状态(Mark Word) 对象头里有个 **Mark Word** 保存锁状态与元信息(哈希、年龄、锁标志等)。HotSpot 用它做“**轻量级**”的快速加锁;必要时再**膨胀**为重锁: * **无锁**:普通 Mark Word(可能含哈希)。 * **轻量级锁**(Thin/Lightweight):把 Mark Word **CAS** 替换为指向栈上“锁记录(Lock Record)”的指针,表示该线程持有;重入时只在栈上叠加计数。 * **重量级锁**(Inflated/Monitor):竞争激烈或自旋失败,锁“膨胀”为 `ObjectMonitor`,包含 `_owner`、`_EntryList`、`_WaitSet` 等结构;阻塞/唤醒用 `park/unpark`。 > 偏向锁(Biased)曾用于无竞争的单线程场景以消除CAS;JDK15默认禁用,JDK18移除,面试可一句话带过。 # 3) 获取与释放(快路径→自旋→阻塞) **获取(简化伪流程):** 1. **尝试轻量级加锁**:当前线程用 CAS 把对象 Mark Word 置为“指向我栈上锁记录”。成功→进入临界区。 2. **自旋**:若失败(可能因并发),先短暂**自旋**等待持有者释放(自适应自旋:历史成功率高就多转几圈)。 3. **膨胀为重量级**:自旋仍失败或发生阻塞条件 → 创建/复用 `ObjectMonitor`,将等待线程入 **EntryList**,`park()` 阻塞,等待持有者 `unpark()`。 **释放:** * 轻量级:把栈上锁记录还原回 Mark Word(CAS),若有等待者可能触发唤醒。 * 重量级:`ObjectMonitor` 的 `_owner` 释放,唤醒(通常唤一个)EntryList 的后继节点竞争。 > HotSpot 不是严格公平锁,存在 **barging**(新来的可能插队获取),吞吐更好但**不保证先来先得**。 # 4) 可重入、等待队列与 `wait/notify` * **可重入**:同一线程重复进入同一把锁会累加“重入计数”(轻锁存于栈锁记录;重锁存于 `ObjectMonitor._recursions`)。 * **`wait/notify` 必须在同步块内**: * `wait()`:当前线程从 **同步队列** 移到 **WaitSet**,并**释放锁**;被 `notify/notifyAll` 唤醒后回到 EntryList 重新竞争锁。 * `notify()`:从 WaitSet 挑一个(或全部)移动到 EntryList,**不等于立刻获得锁**。 # 5) 内存语义(JMM) * **解锁 → 加锁** 之间存在 *happens-before*:解锁前写入对随后获得同一把锁的线程**可见**。 * JVM 在 `monitorenter/monitorexit` 附近插入 **Acquire/Release 屏障**,保证有序与可见性。 * 因此 `synchronized` 既提供**互斥**也提供**可见性**,语义等价于“`volatile` 的可见性 + 互斥进入”。 # 6) 性能相关的几个点 * **自旋 + 自适应**:短临界区不必阻塞,减少上下文切换。 * **锁粗化 / 锁消除**:JIT 基于**逃逸分析**把临近的小锁合并(粗化),或证明对象不逃逸直接去掉锁(消除)。 * **避免在临界区做慢操作**:IO/远程调用/大对象分配会拉长持锁时间,放大竞争。 * **异常安全**:不论正常退出还是异常,JVM 都会确保执行 `monitorexit`(字节码结构保证)。 # 7) 与 `ReentrantLock` 的差异速记 * `synchronized`:语义简单、**自动释放**、不可中断获取、无超时、无多条件队列;JIT 优化好,适合大多数场景。 * `ReentrantLock`:基于 **AQS**,支持**可中断**、**可超时**、**公平策略**、**多个 `Condition`**,功能更丰富。 # 8) 实战建议 * **临界区要短小**;不要在锁内做网络/磁盘。 * 尽量**缩小锁粒度**,必要时拆分锁(读写锁/分段锁)。 * 只在需要的共享资源上加锁;能不共享就用 **ThreadLocal/消息** 解耦。 * `wait()` 配合 `while` 重新检条件,防**虚假唤醒**;`notifyAll()` 用于多个条件可能成立的场景。 一句话总结: `synchronized` 通过 **对象头 Mark Word + CAS + 自旋** 实现轻量竞争,必要时**膨胀为 `ObjectMonitor`** 用队列阻塞/唤醒;它提供**互斥 + 可见性**,是 JVM 级别的“内置锁”,简单可靠,绝大多数同步场景足够好用。
    发布于 2025-08-19 11:32 •IP属地 上海
    举报
    评论
    收藏