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

相关推荐
开源之眼44 分钟前
github star 较多的Java双亲委派机制【类加载的核心内容加星】
java
编程火箭车1 小时前
【Java SE 基础学习打卡】19 运算符(中)
java·java入门·运算符·编程基础·赋值运算符·复合赋值·自增自减
是一个Bug1 小时前
Spring事件监听器源码深度解析
java·数据库·spring
蜂蜜黄油呀土豆1 小时前
ThreadLocal 深度解析:它解决了什么、原理是什么、如何正确使用(含代码与实战建议)
java·并发编程·内存泄漏·threadlocal
45288655上山打老虎1 小时前
【智能指针】
开发语言·c++·算法
毕设源码-郭学长1 小时前
【开题答辩全过程】以 高校教室管理系统为例,包含答辩的问题和答案
java·spring boot
罗不丢1 小时前
UTC,Date,LocalDate转换问题解决方法
java
Klong.k1 小时前
谈谈session、application存储对象
java·tomcat
Moe4881 小时前
Spring Boot启动魔法:SpringApplication.run()源码全流程拆解
java·后端·面试