Java synchronized 锁的 8 个经典问题小结
作者:Knight_AL
一、前言
synchronized 的“锁的 8 个问题”几乎是并发面试必考。题目表面是在问“先打印短信还是邮件”,本质考察的是:对象锁 vs 类锁、同一对象 vs 不同对象、静态同步 vs 普通同步方法 等关键概念。下面用 8 个最典型的小实验,把底层锁定逻辑讲清楚。
二、代码基础模板
资源类 Phone:包含两个同步方法(锁 this)和一个普通方法(不加锁)。
class Phone {
// 发送短信(同步方法:锁 this)
public synchronized void sendSMS() {
System.out.println(Thread.currentThread().getName() + " → 发送短信");
}
// 发送邮件(同步方法:锁 this)
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + " → 发送邮件");
}
// 普通方法(无锁)
public void getHello() {
System.out.println(Thread.currentThread().getName() + " → hello");
}
}
简单试跑:
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(phone::sendSMS, "AA").start();
new Thread(phone::sendEmail, "BB").start();
}
}
三、锁的 8 个问题(逐一验证)
下列 8 个小实验通过微调代码,验证不同锁语义。为更稳定观察先后顺序,部分用到小延迟。
(1)标准访问:先短信还是邮件?
class Phone {
public synchronized void sendSMS() { System.out.println("发送短信"); }
public synchronized void sendEmail() { System.out.println("发送邮件"); }
}
public class Lock8_1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(phone::sendSMS, "AA").start();
new Thread(phone::sendEmail, "BB").start();
}
}
现象:
发送短信
发送邮件
结论: 两个同步方法、同一对象 → 同一把对象锁(this),谁先拿锁谁先执行(通常按启动时机)。
(2)短信方法内停 4 秒:谁先?
class Phone {
public synchronized void sendSMS() {
try { Thread.sleep(4000); } catch (InterruptedException ignored) {}
System.out.println("发送短信");
}
public synchronized void sendEmail() { System.out.println("发送邮件"); }
}
public class Lock8_2 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(phone::sendSMS, "AA").start();
// 小延迟,确保 AA 先启动拿到锁
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
new Thread(phone::sendEmail, "BB").start();
}
}
现象:
发送短信
发送邮件
结论: 同一对象的两个同步方法互斥,AA 持锁期间 BB 必须等待 → 串行。
(3)普通方法 + 同步方法:谁先?
class Phone {
public synchronized void sendSMS() {
try { Thread.sleep(4000); } catch (InterruptedException ignored) {}
System.out.println("发送短信");
}
public void getHello() { System.out.println("hello"); }
}
public class Lock8_3 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(phone::sendSMS, "AA").start();
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
new Thread(phone::getHello, "BB").start();
}
}
现象:
hello
发送短信
结论: 普通方法不加锁,不受同步影响 → 直接执行,通常先于同步方法的输出。
(4)两部手机(两个对象),各自同步方法
public class Lock8_4 {
public static void main(String[] args) {
Phone p1 = new Phone();
Phone p2 = new Phone();
new Thread(p1::sendSMS, "AA").start();
new Thread(p2::sendEmail, "BB").start();
}
}
现象:
发送邮件
发送短信
结论: 每个实例都有独立的对象锁,不同对象互不干扰 → 可以并行。
(5)两个静态同步方法,一部手机
class Phone {
public static synchronized void sendSMS() {
try { Thread.sleep(4000); } catch (InterruptedException ignored) {}
System.out.println("发送短信");
}
public static synchronized void sendEmail() { System.out.println("发送邮件"); }
}
public class Lock8_5 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(Phone::sendSMS, "AA").start();
new Thread(Phone::sendEmail, "BB").start();
}
}
现象:
发送短信
发送邮件
结论: 静态同步方法锁的是类对象 Phone.class(类锁),全类唯一 → 串行。
(6)两个静态同步方法,两部手机
public class Lock8_6 {
public static void main(String[] args) {
Phone p1 = new Phone();
Phone p2 = new Phone();
new Thread(Phone::sendSMS, "AA").start();
new Thread(Phone::sendEmail, "BB").start();
}
}
现象:
发送短信
发送邮件
结论: 仍是同一个 Phone.class 的锁,和多少实例无关 → 串行。
(7)一个静态同步 + 一个普通同步,一部手机
class Phone {
public static synchronized void sendSMS() {
try { Thread.sleep(4000); } catch (InterruptedException ignored) {}
System.out.println("发送短信");
}
public synchronized void sendEmail() { System.out.println("发送邮件"); }
}
public class Lock8_7 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(Phone::sendSMS, "AA").start();
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
new Thread(phone::sendEmail, "BB").start();
}
}
现象:
发送邮件
发送短信
结论: 类锁(Phone.class)与对象锁(this)是两把不同的锁 → 可并行。
(8)一个静态同步 + 一个普通同步,两部手机
public class Lock8_8 {
public static void main(String[] args) {
Phone p1 = new Phone();
Phone p2 = new Phone();
new Thread(Phone::sendSMS, "AA").start();
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
new Thread(p2::sendEmail, "BB").start();
}
}
现象:
发送邮件
发送短信
结论: 类锁(Phone.class)与某个实例的对象锁(p2.this)互不影响 → 并行。
四、八大结论一览
| 编号 | 场景描述 | 锁对象 | 是否同一锁 | 执行特征 |
|---|---|---|---|---|
| 1 | 同一对象两个同步方法 | 对象锁(this) | ✅ 是 | 串行 |
| 2 | 同一对象,一个方法内睡眠 | 对象锁(this) | ✅ 是 | 串行 |
| 3 | 同一对象,一个同步 + 一个普通方法 | 对象锁 + 无锁 | ❌ 否 | 普通先 |
| 4 | 两个对象,各自同步方法 | 两个对象锁 | ❌ 否 | 并行 |
| 5 | 一对象,两个静态同步方法 | 类锁(Phone.class) | ✅ 是 | 串行 |
| 6 | 两对象,两个静态同步方法 | 类锁(Phone.class) | ✅ 是 | 串行 |
| 7 | 一对象:一个静态同步 + 一个普通同步 | 类锁 + 对象锁 | ❌ 否 | 并行 |
| 8 | 两对象:一个静态同步 + 一个普通同步 | 类锁 + 对象锁 | ❌ 否 | 并行 |
五、核心知识小结
- 对象锁:synchronized 实例方法 / synchronized(this) → 锁的是当前实例。
- 类锁:static synchronized / synchronized(Phone.class) → 锁的是类对象,全类唯一。
- 普通方法:不参与加锁。
- 代码块锁:synchronized(obj) 可自定义锁粒度,取决于 obj。
六、要点
- synchronized 锁的是对象,不是方法。
- 实例锁与类锁互不影响;不同实例的对象锁也互不影响。
- 判断是否互斥的核心是:锁对象是否相同。
以上 8 个实验覆盖了 synchronized 的主流锁粒度考点。理解“锁定谁”这一件事,就能解释为什么有些代码会阻塞,有些能并行。
牢记:锁不是给代码加的,而是给对象加的。
到此这篇关于Java synchronized 锁的 8 个经典问题小结的文章就介绍到这了,更多相关Java synchronized 锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
