【002】synchronized关键字及其工作原理
文章目录
一、参考答案
1、互斥锁: synchronized提供了一种锁机制,能够确保同一时刻只有一个线程执行某段代码。
2、对象锁和类锁: 可以锁定对象实例(方法或代码块)或整个类(静态方法)。
3、内存可见性: 保证了锁内操作对其他线程的可见性。
4、锁升级: 在JVM中,synchronized可能经历偏向锁、轻量级锁和重量级锁的升级。
二、实现原理
synchronized 是 Java 中最基本也是最经典的同步机制,它保证了在多线程环境下,共享资源的互斥访问和内存可见性。理解它的实现原理,是掌握 Java 并发编程的关键一步。
2.1 核心概念
在深入原理之前,我们先明确 synchronized 的几个核心作用:
- 互斥性 (Mutual Exclusion):确保同一时刻只有一个线程能执行同步代码块或同步方法。
- 可见性 (Visibility):当一个线程退出同步块时,它对共享变量的修改对其他进入该同步块的线程是可见的, 这是通过 “happens-before” 规则保证的。
- 原子性 (Atomicity):同步块或方法内的操作被当作一个不可分割的整体执行。
synchronized 可以用在以下地方:
- 同步方法:public synchronized void test() {}
- 同步代码块:synchronized(this) {} 或 synchronized(ClassName.class) {}
2.2 JVM层面实现: 字节码与监视器锁 (Monitor)
synchronized 的实现依赖于 JVM 内部的 监视器锁(Monitor) 机制。我们从字节码层面来分析。
✅ 1. 同步代码块(synchronized(this))
当你编写一个同步代码块时,编译器会在字节码中插入 monitorenter 和 monitorexit 两条指令。
public void syncBlock() {
synchronized (this) {
// 临界区代码
System.out.println("Inside synchronized block");
}
}
对应的字节码(简化)
// ...
aload_0 // 将 this 引用压入操作数栈
monitorenter // 获取 this 对象的监视器锁
// ...
getstatic // ...
invokevirtual // 调用 System.out.println
// ...
monitorexit // 释放监视器锁
➡️ 2. 工作流程
- 线程执行到 monitorenter 指令时,会尝试获取该指令参数(即 this 对象)所关联的 Monitor。
- 如果获取成功,线程将持有该 Monitor,继续执行临界区代码。
- 如果获取失败(说明该 Monitor 已被其他线程持有),当前线程会被阻塞,并进入该 Monitor 的 Entry Set (入口集) 队列中等待。
- 当持有 Monitor 的线程执行到 monitorexit 指令时,它会释放 Monitor。此时,JVM 会从 Entry Set 中唤醒一个等待的线程,让它尝试获取 Monitor。
注意:为了保证即使在临界区代码抛出异常时锁也能被释放,编译器会自动生成一个 finally 块,将 monitorexit 指令放在里面。所以你会在字节码中看到不止一个 monitorexit。
2.3 同步方法 (public synchronized void syncMethod())
同步方法的实现略有不同,它没有 monitorenter 和 monitorexit 指令。
public synchronized void syncMethod() {
// 临界区代码
System.out.println("Inside synchronized method");
}
对应的字节码(简化):
public synchronized void syncMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED // <--- 关键在这里
Code:
stack=2, locals=1, args_size=1
// ...
getstatic // ...
invokevirtual // 调用 System.out.println
// ...
2.4 工作原理
当一个方法被 synchronized 关键字修饰时,JVM 会在方法的 方法表(MethodInfo) 中设置 ACC_SYNCHRONIZED 标志。当线程调用这个方法时,JVM 会检查该标志:
- 如果设置了该标志,线程必须先获取与 方法所属对象(对于实例方法是 this,对于静态方法是类的 - Class 对象)相关联的 Monitor。
- 获取成功后,执行方法体。
- 方法执行完毕(无论是正常返回还是异常退出),JVM 会自动释放 Monitor。
无论是同步代码块还是同步方法,其本质都是通过获取和释放 Monitor 来实现同步。同步代码块是显式地在字节码中插入指令,而同步方法是通过方法表中的标志由 JVM 隐式处理。
三、Monitor的内部结构
Monitor 是一个由 JVM 内部实现的对象,你可以把它想象成一个同步工具或锁对象。它的内部结构大致如下:
- Owner (所有者):指向当前持有该 Monitor 的线程。同一时刻,一个 Monitor 只能有一个 Owner。
- Entry Set (入口集):存放等待获取 Monitor 锁而被阻塞的线程队列。当 Owner 释放锁时,会从这里唤醒一个线程。
- Wait Set (等待集):存放调用了 wait() 方法而被阻塞的线程队列。这些线程已经持有了锁,但是因为某些条件不满足而主动释放锁,进入等待状态。当其他线程调用 notify() 或 notifyAll() 时,会从这里唤醒一个或所有线程,它们会重新进入 Entry Set 尝试获取锁。
一个形象的比喻:
- Monitor = 一个会议室。
- Owner = 正在会议室里开会的人。
- Entry Set = 在会议室门口排队等待进去开会的人。
- Wait Set = 开会开到一半,出去休息一下(等待某个信号),等信号来了再回来继续开会的人。他们- 休息时,会议室可以让给别人用。
四、锁的优化:从重量级锁到轻量级锁
在 JDK 1.6 之前,synchronized 的实现完全依赖于操作系统的 互斥量(Mutex),这是一种重量级锁。每次线程的阻塞和唤醒都需要从用户态切换到内核态,开销非常大。
为了提升性能,JDK 1.6 及以后版本对 synchronized 进行了一系列优化,引入了偏向锁和轻量级锁,使得 synchronized 在低竞争场景下的性能有了质的飞跃。
这些锁的实现都依赖于对象头中的 Mark Word.
4.1 对象头
- 在 JVM 中,每个对象在内存中都有一个对象头,它包含了对象的元数据信息。synchronized 锁的状态就存储在对象头的 Mark Word 部分。
- Mark Word 的结构是动态变化的,它会根据对象的状态(无锁、偏向锁、轻量级锁、重量级锁)来存储不同的信息
Mark Word (32 位 JVM 示例):
4.2 总结
synchronized 的实现原理可以概括为以下几点:
- 核心是 Monitor:synchronized 的同步语义是通过 JVM 内部的 监视器锁(Monitor) 来实现的。
- 字节码层面:
- 同步代码块通过 monitorenter 和 monitorexit 指令来获取和释放 Monitor。
- 同步方法通过方法表中的 ACC_SYNCHRONIZED 标志,由 JVM 隐式处理 Monitor 的获取和释放。
- 对象头的作用:锁的状态(无锁、偏向锁、轻量级锁、重量级锁)存储在对象头的 Mark Word 中。
- 锁的优化:为了提升性能,JDK 1.6 引入了偏向锁和轻量级锁。锁会根据竞争情况自动升级,从无锁到偏向锁,再到轻量级锁,最后到重量级锁,以在不同场景下达到最佳性能。
- 内存可见性:synchronized 除了保证互斥性,还通过 “happens-before” 规则保证了同步块内外的内存可见性。
更多推荐



所有评论(0)