synchronized锁的是对象的监视器(monitor),不是代码、方法或变量本身;一个对象只有一个monitor,多线程竞争同一对象的synchronized才互斥,锁不同对象则完全不互斥。

Java里synchronized到底锁的是谁
它锁的是对象的监视器(monitor),不是代码块、不是方法名、更不是变量本身。一个对象只有一个monitor,所以多个线程竞争同一个对象的synchronized方法或代码块时才会互斥;如果锁的是不同对象,哪怕代码一模一样,也完全不互斥。
常见误判场景:
- 写
synchronized(this),但每次 new 一个新实例,等于每条线程都拿自己的锁,毫无互斥效果 - 静态方法用
synchronized,实际锁的是当前类的Class对象(如MyClass.class),和实例方法的锁完全无关 - 在 Spring 管理的 Bean 中,若作用域是 prototype,每个请求拿到的都是新对象,
synchronized实际失效
synchronized 方法 vs synchronized(this) 代码块
二者语义等价,但粒度和可读性差异明显。方法级锁会把整个方法体包进去,哪怕只有几行需要保护,也会拖慢其他不相关逻辑;而代码块可以精确控制临界区范围。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 优先用
synchronized(this)或synchronized(shareLockObj)显式指定锁对象,避免隐式锁带来的歧义 - 不要在 public 方法上直接加
synchronized,除非你明确知道调用方共享的是同一实例 - 若需保护静态资源(如缓存计数器),必须用
synchronized(MyClass.class),不能靠实例锁
示例:下面两段逻辑行为一致,但后者更清晰、更易维护
public synchronized void increment() {
count++;
}
public void increment() {
synchronized(this) {
count++;
}
}
为什么synchronized有时像没起作用
最常被忽略的原因是锁对象不一致。比如以下典型错误:
- 用字符串字面量当锁:
synchronized("key")—— JVM 可能对相同字面量做字符串常量池优化,导致意外共享;更糟的是,其他代码也可能用同样字符串,引发跨模块干扰 - 用可变对象当锁,且该对象被外部修改或重新赋值,导致后续同步块锁的是另一个对象
- 在 getter/setter 中加
synchronized,但业务代码绕过这些方法直接操作字段(尤其在非 private 字段+非 final 场景下)
验证是否真互斥:可在临界区内加 Thread.sleep(100),再开两个线程反复调用,观察输出是否交错。交错即说明没锁住。
synchronized 和 ReentrantLock 的关键区别在哪
核心不是“哪个更好”,而是“能不能响应中断”“要不要超时”“是否需要条件队列”。synchronized 是 JVM 层原语,自动释放、不可中断、不支持超时;ReentrantLock 是 API 层实现,要手动 lock()/unlock(),但提供 tryLock(long, TimeUnit) 和 newCondition()。
选型判断点:
- 简单同步、无复杂等待逻辑 → 用
synchronized,JVM 优化成熟(偏向锁、轻量级锁、重量级锁升级路径清晰) - 需要中断等待线程(如任务取消)、或必须限定等待时间 → 必须用
ReentrantLock - 要实现多路等待(如生产者/消费者共用一个锁但分不同条件)→ 只能用
ReentrantLock.newCondition()
注意:synchronized 不可重入?错。它是可重入的——同一个线程可多次进入自己已持有的锁,计数器累加,退出时逐层减。









