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

相关推荐
夏幻灵3 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
程序猿编码4 小时前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
bkspiderx5 小时前
Qt 插件机制:动态扩展应用功能的核心框架
开发语言·qt·元数据·qt 插件·qpluginloader
代码什么用5 小时前
Spring基础使用
java·后端·spring
程序员老陆5 小时前
深入理解 C++ thread_local:线程私有存储的正确打开方式
开发语言·c++·程序设计
沐晓时光5 小时前
C语言入门,深入理解指针(3)
c语言·开发语言
Su米苏6 小时前
Spring AI 中MCP 与普通 @Tool的区别
java·人工智能·spring
码云数智-园园6 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
老板一杯拿铁6 小时前
Java 异常体系详解:从字节码原理到最佳实践
java
蜗牛互联网6 小时前
Java 17 HttpClient调用文件转写API的超时与失败回退
java·人工智能·后端