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

相关推荐
啊阿狸不会拉杆20 小时前
《数字图像处理》第 4 章 - 频域滤波
开发语言·python·数字信号处理·数字图像处理·频率域滤波
他们叫我技术总监20 小时前
Python 列表、集合、字典核心区别
android·java·python
江沉晚呤时20 小时前
从零实现 C# 插件系统:轻松扩展应用功能
java·开发语言·microsoft·c#
梁下轻语的秋缘20 小时前
ESP32-WROOM-32E存储全解析:RAM/Flash/SD卡读写与速度对比
java·后端·spring
wanzhong233320 小时前
开发日记8-优化接口使其更规范
java·后端·springboot
Knight_AL20 小时前
Java 多态详解:概念、实现机制与实践应用
java·开发语言
C雨后彩虹20 小时前
volatile 实战应用篇 —— 典型场景
java·多线程·并发·volatile
xie_pin_an20 小时前
从二叉搜索树到哈希表:四种常用数据结构的原理与实现
java·数据结构
Omigeq20 小时前
1.2.1 - 图搜索算法(以A*为例) - Python运动规划库教程(Python Motion Planning)
开发语言·python·机器人·图搜索算法
资深流水灯工程师20 小时前
基于Python的Qt开发之Pyside6 串口接收数据被分割的解决方案
开发语言·python·qt