彻底理解 synchronized:实例锁、类锁与自定义锁的原理和最佳实践

引言

多线程同步是 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 是公开锁,自定义锁是内部锁。
所以推荐用自定义锁。

相关推荐
Scott9999HH2 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔2 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社3 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海3 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖4 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区6 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大7 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai7 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说7 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json