
一、synchronized的三大特性深度解析原文提到了原子性、可见性、有序性这里补充JMM层面的实现机制这是面试官最想听到的深度。1.1 原子性的底层保障内存屏障synchronized的原子性并非简单“互斥”而是通过JVM插入的内存屏障实现的。在synchronized代码块前后JVM会插入以下屏障屏障类型插入位置作用LoadLoad屏障获取锁后禁止后续读操作重排序到锁之前StoreStore屏障释放锁前确保写操作在释放锁前完成LoadStore屏障获取锁后禁止读操作重排序到写操作之前StoreLoad屏障释放锁后确保所有写操作对其他线程可见关键点这些屏障确保了锁内的代码像一个不可分割的“事务”被执行。1.2 可见性的实现机制Happens-Before原则synchronized满足JMM的Happens-Before规则中的管程锁定规则对一个锁的解锁操作happens-before于随后对同一个锁的加锁操作。这意味着线程A释放锁时会将工作内存中的共享变量刷新到主内存线程B获取同一把锁时会从主内存重新加载共享变量因此线程B一定能看到线程A修改的结果底层实现在x86架构上monitorexit指令会触发LOCK前缀指令该指令会强制将CPU缓存写入主内存并让其他CPU的缓存行失效。1.3 有序性的保障禁止指令重排序synchronized禁止重排序的范围仅限于被锁定的代码块内部而非整个程序。JVM通过以下机制保证锁获取屏障确保获取锁前的操作不会重排序到锁内锁释放屏障确保锁内的操作不会重排序到锁外示例javaint a 0; synchronized (lock) { a 1; // 不会与锁外的操作重排序 } int b a; // 能保证读到a1二、对象头与Monitor的深度拆解原文提到了对象头和Monitor这里补充64位JVM的对象头结构和Monitor的内部实现。2.1 64位JVM的Mark Word结构64位JVM的Mark Word长度为8字节64位不同锁状态下结构不同锁状态锁标志位64位Mark Word结构无锁01unused:25 | hashCode:31 | cms_free:1 | age:4 | biased_lock:0 | lock:01偏向锁01thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:01轻量级锁00ptr_to_lock_record:62 | lock:00重量级锁10ptr_to_monitor:62 | lock:10GC标记11用于垃圾回收标记关键字段说明hashCode对象的哈希码未重写时ageGC分代年龄4位最大15biased_lock是否偏向锁标志thread持有偏向锁的线程IDptr_to_lock_record指向栈中锁记录的指针ptr_to_monitor指向Monitor对象的指针2.2 Monitor的内部实现Monitor是JVM内部的一个C对象ObjectMonitor其核心数据结构cppclass ObjectMonitor { volatile markOop _header; // 对象头 void* _owner; // 当前持有锁的线程 ObjectWaiter* _WaitSet; // 等待队列wait()方法 ObjectWaiter* _EntryList; // 阻塞队列等待获取锁 volatile intptr_t _count; // 锁计数器重入次数 // ... };执行流程线程尝试获取锁时CAS将_owner设置为当前线程如果_owner已被占用线程进入_EntryList阻塞持有锁的线程调用wait()进入_WaitSet释放锁其他线程调用notify()将_WaitSet中的线程移动到_EntryList三、锁升级机制的深入细节原文介绍了锁升级的四个阶段这里补充每个阶段的触发条件和底层实现细节。3.1 偏向锁的撤销与批量重偏向偏向锁撤销不是简单的“升级”而是有复杂的批量机制场景行为原因第1-19次撤销偏向锁撤销升级为轻量级锁轻度竞争避免重偏向开销第20次撤销触发批量重偏向JVM认为该类可能有偏向价值第40次撤销触发批量撤销该类禁用偏向锁JVM认为该类不适合偏向锁配置参数-XX:BiasedLockingStartupDelay0JVM启动后立即启用偏向锁默认延迟4秒-XX:-UseBiasedLocking禁用偏向锁高并发场景可禁用减少撤销开销3.2 轻量级锁的自旋优化轻量级锁使用CAS自旋等待自旋次数不是固定的自旋策略说明固定自旋JDK 1.6之前默认自旋10次自适应自旋JDK 1.6JVM根据历史自旋成功率动态调整自旋锁膨胀自旋失败一定次数后升级为重量级锁自适应自旋逻辑如果上次自旋成功过本次自旋次数可以更多如果上次自旋失败本次自旋次数减少快速升级3.3 锁升级的完整流程图text对象创建 ↓ 无锁状态 ↓ 线程首次获取锁 ↓ ┌──────────────┴──────────────┐ ↓ ↓ 偏向锁获取成功 偏向锁撤销(有竞争) (Mark Word记录线程ID) ↓ ↓ 轻量级锁 同一线程重入 ↓ (无需CAS) CAS自旋获取锁 ↓ ↓ 无锁竞争 自旋失败多次 ↓ ↓ └──────────────┬──────────────┘ ↓ 重量级锁 (Monitor内核态阻塞)四、synchronized的性能优化锁消除与锁粗化除了锁升级JVM还有两个重要的synchronized优化机制很多开发者不知道。4.1 锁消除Lock EliminationJVM的逃逸分析发现某个锁对象永远不会被其他线程访问时会消除该锁。示例javapublic void test() { Object lock new Object(); // 局部对象线程私有 synchronized (lock) { System.out.println(这个锁会被消除); } }JVM参数-XX:EliminateLocksJDK 1.8默认开启4.2 锁粗化Lock CoarseningJVM会将连续的同步代码块合并为一个大的同步块减少锁获取/释放次数。示例优化前javapublic void test() { for (int i 0; i 100; i) { synchronized (lock) { count; } } }优化后锁粗化javapublic void test() { synchronized (lock) { for (int i 0; i 100; i) { count; } } }五、synchronized vs ReentrantLock 深度对比原文给出了对比表格这里补充底层实现和适用场景的深度分析。5.1 底层实现对比维度synchronizedReentrantLock实现层级JVM层C实现JDK层Java实现锁获取monitorenter指令Unsafe类的CAS线程阻塞进入_EntryList阻塞调用LockSupport.park()公平性默认非公平不可配置可配置公平/非公平条件变量只有一个等待队列wait/notify可有多个Condition5.2 功能对比功能synchronizedReentrantLock可重入✅✅中断响应❌无法中断等待锁的线程✅lockInterruptibly()超时获取❌✅tryLock(timeout)公平锁❌✅构造参数多条件等待❌只有wait/notify✅多个Condition锁状态查询❌✅isHeldByCurrentThread()5.3 性能对比与选择建议性能对比JDK 1.8JVM经过大量优化低竞争场景synchronized ≈ ReentrantLock甚至略优高竞争场景两者接近但ReentrantLock的tryLock可以避免阻塞选择建议优先使用synchronized代码简洁JVM持续优化满足90%场景使用ReentrantLock的场景需要可中断获取锁lockInterruptibly需要超时获取锁tryLock需要公平锁需要多个条件变量Condition六、wait/notify机制与synchronized的配合synchronized与wait()/notify()是Java经典的生产者-消费者模式基础很多开发者用不好。6.1 核心规则必须先获取锁wait()/notify()必须在synchronized代码块中调用调用wait()会释放锁线程进入_WaitSet释放Monitor调用notify()不释放锁只将等待线程移动到_EntryList当前线程继续执行直到释放锁6.2 正确使用示例javapublic class WaitNotifyDemo { private final Object lock new Object(); private boolean condition false; // 等待线程 public void waitForCondition() throws InterruptedException { synchronized (lock) { while (!condition) { // 必须用while不能用if lock.wait(); // 释放锁进入等待 } // 条件满足继续执行 } } // 唤醒线程 public void signalCondition() { synchronized (lock) { condition true; lock.notifyAll(); // 唤醒所有等待线程notify只能唤醒一个 } } }为什么必须用while而不是if被唤醒后条件可能再次被其他线程改变while可以重新检查条件避免“虚假唤醒”6.3 常见错误notify丢失java// 错误示例 synchronized (lock) { if (condition) { lock.wait(); // 条件不满足才等待 } // 但这里可能condition被其他线程改变 } // 正确做法while检查 synchronized (lock) { while (condition) { // 条件满足时等待 lock.wait(); } }七、面试高频问题与深度解析7.1 被synchronized修饰的方法抛出异常后锁会释放吗答案会释放。原因JVM编译时会在异常表中插入monitorexit指令确保异常路径也能释放锁。这就是字节码中有两个monitorexit的原因。7.2 synchronized锁的是对象还是代码答案锁的是对象确切地说是对象的Monitor不是代码。证明javapublic class Test { public synchronized void method1() { } public synchronized void method2() { } }同一个实例调用method1和method2两个方法会互斥——因为锁的是同一个this对象。7.3 String作为锁对象有什么问题问题字符串常量池lock字面量在常量池中可能被其他代码意外共享不可变性虽然不可变但不同包名类可能使用相同字符串正确做法java// 不推荐 private final String lock lock; // 推荐 private final Object lock new Object();7.4 synchronized与volatile的区别维度synchronizedvolatile原子性✅ 保证❌ 不保证可见性✅ 保证✅ 保证有序性✅ 保证✅ 保证禁止重排序锁机制互斥锁无锁性能开销大开销小适用场景复合操作如count单一读/写操作八、总结synchronized的核心思维框架textsynchronized 互斥 可见性 有序性 互斥的实现 ├── 字节码monitorenter/monitorexit ├── 对象头Mark Word锁状态位 └── Monitor_owner、_EntryList、_WaitSet 锁升级机制 ├── 无锁 → 偏向锁单线程重复获取 ├── 偏向锁 → 轻量级锁少量线程竞争 └── 轻量级锁 → 重量级锁激烈竞争、自旋失败 性能优化 ├── 锁消除逃逸分析消除无用锁 ├── 锁粗化合并连续同步块 └── 自适应自旋动态调整自旋次数 使用原则 ├── 锁对象不可变final修饰 ├── 锁粒度最小化优先用代码块 ├── 保护静态资源用类锁 └── 避免死锁统一锁顺序核心结论synchronized是JVM内置锁自动获取释放使用简单锁升级机制是JDK 1.6的核心优化按需升级减少开销锁对象的选择决定锁的作用范围实例锁 vs 类锁锁粒度控制直接影响并发效率优先用代码块JDK 1.8之后synchronized性能已接近ReentrantLock简单场景优先使用synchronized虽然基础但深入理解其底层实现、锁升级机制、JVM优化是成为Java并发编程高手的必经之路。希望这份深度整理能帮你建立起完整的synchronized知识体系写出更安全、更高效的并发代码。