一、概述
synchronized 是 Java 内置的关键字,用于实现线程同步。在并发编程中,它能保证原子性、可见性和有序性,是最常用的同步手段之一。
JDK1.6 之后,JVM 对 synchronized 做了大量优化(如偏向锁、轻量级锁、自旋、锁消除等),性能已经有了极大提升,因此在实际开发中完全可以放心使用。
二、synchronized 的语义
-
互斥性
确保同一时间只有一个线程能进入同步块或同步方法。 -
可见性
线程在释放锁时,会把本地工作内存中的数据刷新到主内存;获取锁的线程会从主内存中重新读取数据。 -
有序性
JVM 在进入与退出同步块时,会插入内存屏障,禁止指令重排序,保证临界区内外的执行顺序。
三、实现基础
-
对象头与 Mark Word
HotSpot 虚拟机中,每个对象都有一个对象头。对象头中的 Mark Word 用于存储:- 哈希值(HashCode)
- GC 年龄
- 锁状态(无锁、偏向锁、轻量级锁、重量级锁)
锁的升级,本质就是 Mark Word 的变化。
-
Monitor 与字节码指令
- 同步代码块:编译成
monitorenter/monitorexit指令。 - 同步方法:通过方法的
ACC_SYNCHRONIZED标志实现。 - Monitor 内部保存了锁的持有者和重入次数,因此
synchronized是可重入锁。

- 同步代码块:编译成
四、锁的升级过程
- 无锁状态:对象未被任何线程加锁。
- 偏向锁:无竞争情况下,锁偏向第一个获取它的线程。再次进入时只需校验线程 ID,无需 CAS。若其他线程竞争,偏向锁会被撤销。
- 轻量级锁:当多个线程竞争时,尝试使用 CAS 将对象头的 Mark Word 指向线程栈中的 Lock Record。失败后进入自旋等待。
- 重量级锁:竞争激烈时,自旋失败,锁会膨胀为重量级锁,线程阻塞,依赖操作系统调度。
升级路径:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(一般不可逆)。
五、轻量级锁的加锁与解锁
1. 加锁过程
-
当线程进入同步块时,JVM 在当前线程的栈帧中创建一份锁记录(Lock Record),保存对象头中 Mark Word 的拷贝,称为 Displaced Mark Word。
-
尝试使用 CAS 将对象头中的 Mark Word 替换为指向 Lock Record 的指针,同时在 Lock Record 中记录原始 Mark Word。
-
如果 CAS 成功,线程获取轻量级锁,对象进入轻量级锁定状态。
-
如果失败:
- 检查对象头是否已经指向当前线程的栈帧,若是则说明线程重入,可以直接进入;
- 否则说明有竞争,线程会进行自旋,若多次 CAS 失败,则升级为重量级锁。
2. 解锁过程
- 通过 CAS 尝试将 Displaced Mark Word 恢复到对象头。
- 如果 CAS 成功,解锁完成。
- 如果 CAS 失败,说明锁已经升级为重量级锁,此时需要在释放时通知其他等待线程重新竞争。
总结:轻量级锁通过 CAS 和自旋来避免阻塞,性能优于重量级锁。

六、等待与通知机制
-
wait():当前线程释放锁,进入等待状态,直到被唤醒。
-
notify():唤醒一个正在等待的线程。
-
notifyAll():唤醒所有等待线程,让它们重新竞争锁。
-
注意事项:
wait/notify必须在同步块中调用,否则会抛IllegalMonitorStateException。- 推荐用
while循环检查条件,避免伪唤醒。
七、JVM 对 synchronized 的优化
-
锁消除
编译器在 JIT 阶段发现锁对象不会被共享时,会直接移除无用的同步。 -
锁粗化
如果多个连续的同步块作用于同一个对象,会合并成一个更大的同步块,减少加解锁次数。 -
自适应自旋
当线程获取轻量级锁失败时,JVM 会先自旋等待一段时间。如果等待期间锁被释放,就可以避免线程阻塞。自旋时间会根据历史情况自适应调整。 -
批量撤销偏向锁
当某个类的对象频繁发生锁竞争,JVM 会关闭该类的偏向锁,或在安全点进行批量撤销,减少偏向锁的撤销开销。
八、synchronized 的使用方式
-
修饰实例方法:锁的是当前对象实例。
public synchronized void foo() { ... } -
修饰静态方法:锁的是当前类的 Class 对象。
public static synchronized void bar() { ... } -
同步代码块:锁的是括号中指定的对象。
synchronized (lock) { ... }
九、与 ReentrantLock 的对比
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现 | JVM 关键字 | JDK API |
| 公平锁 | 不支持 | 支持 |
| 可中断 | 不支持 | 支持 |
| 条件队列 | 单一(wait/notify) | 多条件(Condition) |
| 锁释放 | 自动释放 | 必须手动 unlock |
| 优化 | 偏向、轻量级、自旋 | 依赖实现 |
结论:简单场景优先用 synchronized,需要更灵活的功能时使用 ReentrantLock。
十、常见问题与注意点
-
异常是否释放锁?
会。线程退出同步块(包括异常)时,锁都会被释放。 -
锁能否降级?
一般不能。轻量级锁升级到重量级锁后不会回退。 -
wait 是否释放锁?
会,wait()会释放锁并进入等待,唤醒后重新竞争锁。 -
锁对象选择
推荐使用private final Object lock = new Object();。不要使用this或公共对象,避免意外竞争和死锁。
十一、总结
synchronized 是 Java 并发的基石。
它通过 对象头 Mark Word + Monitor 实现锁机制,并通过 偏向锁 → 轻量级锁 → 重量级锁 的升级路径来优化性能。轻量级锁利用 CAS 和自旋避免了阻塞,重量级锁则保证了高竞争场景下的稳定性。
理解其加解锁原理、等待通知机制、以及 JVM 的优化策略,能帮助我们写出更高效、更健壮的并发代码。在简单场景下,synchronized 足够安全可靠;在复杂场景下,可以结合 ReentrantLock 提供更灵活的控制。
666

被折叠的 条评论
为什么被折叠?



