引言
多线程同步是 Java 并发编程的核心,而 synchronized 是最基础也最容易用错的关键字。
很多人知道 synchronized 可以锁 this、可以锁类、也可以锁任意对象,但:
-
它们的作用域到底有什么区别?
-
哪些场景会互斥?
-
为什么不建议用 synchronized(this)?
-
为什么"自定义锁"才是专业写法?
这篇文章带你彻底弄清楚 synchronized 锁对象的本质逻辑,让你在任何复杂场景下都能做出正确判断。
一、synchronized 的唯一真相:锁的是"对象"
很多人误以为 synchronized 锁的是一段代码。
其实不是。
synchronized 锁的是 ( ) 中的那个对象。
谁先拿到这个对象的锁,谁就先执行,其它线程必须等待。
因此:
-
锁对象相同 → 互斥
-
锁对象不同 → 不互斥
所有行为都由此决定。
二、三种常见锁方式:this / Class / 自定义锁
同步代码的行为取决于"锁对象是谁"。
最常见的三种锁方式如下:
1)synchronized(this):实例锁(对象级别锁)
写法:
java
public synchronized void foo() { ... }
// 等价于:
public void foo() {
synchronized(this) { ... }
}
行为:
- 锁对象是 this(当前实例)
- 多个线程要互斥 → 必须使用同一个对象实例
- 多个不同实例之间 不互斥
使用场景:
- 保护某个对象的内部状态(balance、count、state)
- 简单同步需求
⚠️ 风险(关键点):
this 是公开的对象,外部也能拿到它。
例如:
java
synchronized(poolInstance) {
poolInstance.getConnection(); // getConnection() 内部是 synchronized(this)
}
结果:
- 外部先锁住 this
- 内部想再锁 this → 进入不了 → 被卡住
- 同步逻辑被外部干扰
- 甚至导致死锁
这就是 synchronized(this) 是危险写法 的原因。
2)synchronized(A.class):类锁(全局级别锁)
写法:
synchronized(A.class) { ... }
行为:
- 锁对象是 A.class(唯一、全局共享)
- 所有 A 的实例互斥
- 任何线程只要锁 A.class → 所有实例都必须排队
使用场景:
- 保护静态变量
- 多实例之间需要互斥的场景
⚠️ 缺点:
- 粒度过大(性能差)
- A.class 也是公开对象,外部代码也能锁它
- 也存在被外部同步代码干扰的风险
3)自定义锁:内部锁(private lock)
专业代码最推荐的写法。
写法:
java
private final Object lock = new Object();
public void foo() {
synchronized(lock) { ... }
}
行为:
- 锁对象是 private 的 lock
- 外部无法访问 lock
- 外部不会影响内部同步
- 不会出现 synchronized(this) 的潜在死锁问题
优势(最重要):
✔ 安全:外部拿不到 lock → 无法干扰
✔ 高性能:可以拆分多个锁,提高并发
✔ 清晰:锁与业务逻辑分离,语义明确
✔ 灵活:可以决定多个实例是否共享锁
示例:拆分锁提高并发性能
java
private final Object balanceLock = new Object();
private final Object pointLock = new Object();
public void updateBalance() {
synchronized(balanceLock) { ... }
}
public void updatePoint() {
synchronized(pointLock) { ... }
}
余额与积分互不影响,提高吞吐量。
三者对比(最关键表格)
| 写法 | 锁对象 | 多实例互斥 | 是否会被外部干扰 | 性能 | 场景 |
|---|---|---|---|---|---|
| synchronized(this) | 实例 | ❌ 不互斥 | ✔ 会(危险) | 一般 | 简单对象同步 |
| synchronized(A.class) | 类对象 | ✔ 全互斥 | ✔ 会(也危险) | 差 | 静态数据,全局锁 |
| synchronized(lock) | private 对象 | 由你决定 | ❌ 不会(安全) | 最佳 | 推荐的专业写法 |
四、为什么 this 锁会被外部干扰?(最常见误区)
内部:
java
public synchronized void foo() {
// synchronized(this)
}
外部:
java
A a = new A();
synchronized(a) { // 外部锁 this
a.foo(); // foo() 内部也要锁 this → 被卡住
}
内部与外部争夺的是同一把锁:
this == aInstance
外部先拿锁 → 内部无法进入 → 产生阻塞或死锁。
五、自定义锁为何完全避免这个问题?
内部:
java
private final Object lock = new Object();
public void foo() {
synchronized(lock) { ... }
}
外部:
java
synchronized(a) { // 外部锁 this
a.foo(); // 内部锁 lock,两者不同 → 不冲突
}
锁对象不同:
java
外部锁 = this
内部锁 = lock(private)
永远不会冲突。
→ 不会死锁
→ 不会阻塞
→ 不会被外部影响
这就是自定义锁成为最佳实践的核心原因。
六、如何选择?
1)什么时候用 synchronized(this)?
- 简单代码
- 内部类/不暴露给外部的对象
- 不担心外部锁 this
2)什么时候用 synchronized(A.class)?
- 保护静态变量
- 全局互斥场景
3)什么时候用自定义锁?(95%场景)
- 并发访问对象内部状态
- 不希望外部影响内部同步
- 高并发场景需要拆锁
- 库/框架层代码(必须安全)
- 复杂同步逻辑需要灵活控制锁粒度
七、总结句(背下来即可)
synchronized(this):锁实例,外部能干扰(危险)
synchronized(Class):锁类,全局互斥(粒度大)
synchronized(lock):锁 private 对象,最安全最专业
最后,把这句话刻在脑子里:
锁对象相同才互斥。
this/Class 是公开锁,自定义锁是内部锁。
所以推荐用自定义锁。