彻底理解 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 是公开锁,自定义锁是内部锁。
所以推荐用自定义锁。

相关推荐
Chester_19992 小时前
CSP202203C.计算资源调度器
开发语言·数据结构·c++·蓝桥杯
EXI-小洲3 小时前
Java 操作 Word:字符串替换、图片插入、动态生成表格与API接口下载
java·开发语言·spring boot·word
会周易的程序员4 小时前
给 PLC 写一个字节码虚拟机:STVM 虚拟机架构设计
开发语言·c++·虚拟机·软plc·iec61131·stvm
机器视觉知识推荐、就业指导4 小时前
为什么老说“纯 Qt 没啥就业市场”?
开发语言·qt
telepan4 小时前
Qt 开发避坑与性能指南:如何优雅、安全地定义全局常量字符串?
开发语言·qt
m0_695251494 小时前
Qt MaintenanceTool 使用国内镜像加速下载(超详细教程)
开发语言·qt
2601_961901704 小时前
SpringBoot使用Nacos进行application.yml配置管理
java·spring boot·spring
何以解忧,唯有..5 小时前
LangChain 工具调用(Tool Calling)实战指南
java·前端·langchain
大衛說5 小时前
《Python 从入门到精通》系列总览与学习路线
开发语言·python·学习
QQ_21696290966 小时前
【源码编号:project79475】SpringBoot校内二手交易平台:商品发布、分类检索、留言交流、订单管理全流程实战
java·spring boot·后端