synchronized 是 Java 中用于实现线程同步的关键字,确保同一时刻只有一个线程可以执行被修饰的代码块或方法。其核心原理基于 Java 对象头中的监视器(Monitor)和锁机制,下面从底层实现、锁升级、内存语义三个层面详细解析:
1. 底层实现:Monitor 与对象头
Monitor(监视器)
- 本质:每个 Java 对象都可以关联一个 Monitor,它是实现同步的基础,底层由 C++ 的
ObjectMonitor类实现。 - 作用:Monitor 是一个同步工具,相当于一个许可证,线程只有获取到 Monitor 才能执行同步代码,执行完毕后释放 Monitor。
对象头(Object Header)
- Java 对象在内存中的布局分为三部分:对象头、实例数据、对齐填充。
- Mark Word:对象头的一部分,用于存储对象的哈希码、分代年龄和锁状态信息。锁状态不同时,Mark Word 的存储内容会发生变化:
- 无锁:存储对象的哈希码和分代年龄。
- 轻量级锁:存储指向线程栈中锁记录的指针。
- 重量级锁:存储指向 Monitor 的指针。
2. 锁的升级过程
JDK 6 之后,synchronized 进行了大量优化,引入了偏向锁、轻量级锁、重量级锁的升级机制,以减少线程竞争带来的性能损耗。锁的升级过程是不可逆的:
偏向锁(Biased Locking)
- 适用场景:只有一个线程访问同步块的情况。
- 原理:
- 当第一个线程访问同步块并获取锁时,会在 Mark Word 中记录该线程的 ID(偏向线程 ID)。
- 后续该线程再次进入同步块时,无需任何同步操作,直接获取锁,效率极高。
- 撤销条件:当有其他线程尝试竞争偏向锁时,持有偏向锁的线程会被挂起,偏向锁升级为轻量级锁。
轻量级锁(Lightweight Lock)
- 适用场景:多个线程交替访问同步块,无实际竞争。
- 原理:
- 线程进入同步块前,JVM 会在当前线程的栈帧中创建一个锁记录(Lock Record)。
- 通过 CAS(Compare and Swap)操作尝试将 Mark Word 更新为指向锁记录的指针:
- 成功:获取轻量级锁。
- 失败:表示有其他线程竞争,锁升级为重量级锁。
- 释放锁:通过 CAS 将 Mark Word 恢复为无锁状态。
重量级锁(Heavyweight Lock)
- 适用场景:多个线程同时竞争锁。
- 原理:
- 锁升级为重量级锁后,Mark Word 存储指向 Monitor 的指针。
- 未获取到锁的线程会被阻塞(进入 Monitor 的 EntryList),释放 CPU 资源。
- 锁释放时,会唤醒 EntryList 中的线程重新竞争。
- 缺点:线程阻塞和唤醒涉及用户态与内核态的切换,性能开销大。
3. 同步方法与同步块的实现差异
同步方法(synchronized method)
- 隐式实现:通过方法修饰符
synchronized实现。 - 原理:JVM 通过方法表中的
ACC_SYNCHRONIZED标志来识别同步方法。调用时,JVM 会自动检查该标志,获取当前对象或类的 Monitor 后执行方法。
同步块(synchronized(this))
- 显式实现:通过
synchronized代码块实现。 - 原理:
- 进入同步块前,执行
monitorenter指令,尝试获取 Monitor。 - 退出同步块时,执行
monitorexit指令(正常退出和异常退出各有一个),释放 Monitor。
- 进入同步块前,执行
4. 内存语义与 Happens-Before 规则
synchronized 不仅保证了线程互斥,还具有内存可见性的语义:
- 解锁(释放 Monitor):JVM 会将当前线程的本地内存中的共享变量刷新到主内存。
- 加锁(获取 Monitor):JVM 会将主内存中的共享变量最新值更新到当前线程的本地内存。
- Happens-Before 规则:对一个 Monitor 的解锁操作 Happens-Before 后续对同一个 Monitor 的加锁操作,确保了可见性。
5. 性能优化建议
- 减少锁的粒度:避免对整个方法加锁,优先使用同步块。
- 降低锁的持有时间:将不需要同步的代码移出同步块。
- 优先使用偏向锁/轻量级锁场景:对于单线程或交替执行的同步代码,性能提升显著。
- 考虑替代方案:高并发场景下,可使用
ReentrantLock、ConcurrentHashMap等更灵活的同步工具。
总结
synchronized 的核心原理是基于 Monitor 和对象头的锁机制,通过锁升级策略(偏向锁 → 轻量级锁 → 重量级锁)在不同场景下平衡性能和线程安全。理解其底层实现有助于写出更高效、更安全的多线程代码。