synchronized锁的是对象实例而非变量名或值,锁对象被替换、非单例、被重赋值或为局部变量时均会导致锁失效;应使用static final锁对象或static synchronized方法确保互斥。

这个问题很典型:synchronized 锁的是对象的引用,不是变量名,也不是值。一旦锁对象本身被替换成另一个新对象,原来那个锁就“断连”了,后续线程拿到的是新对象,彼此不互斥——锁看似还在,实际已失效。
锁对象被隐式替换:Integer、String 等包装类/不可变对象
最常见于用 Integer、String 甚至 Long 这类不可变类型作锁对象:
-
Integer i = 0;→ 初始指向常量池中Integer.valueOf(0)的缓存对象(-128 ~ 127 范围内) - 执行
i++时,等价于i = Integer.valueOf(i.intValue() + 1) - 当超出缓存范围(比如从 127 变成 128),
valueOf()返回全新堆对象,i引用已变 - 下一次
synchronized(i)锁的已是不同对象,前一个线程释放的锁对它无效
同理,String s = "abc"; s += "def"; 后,s 指向新字符串对象,原锁丢失。若要用字符串做锁,必须显式调用 s.intern() 强制复用常量池实例——但要注意内存和 GC 风险。
锁对象非单例:多个实例各自持锁
方法声明为 public synchronized void doWork(),锁对象是 this。如果业务中创建了多个对象实例(比如 Spring 默认 prototype 作用域、或每次 new 出新对象),每个线程调用不同实例的方法,就等于在不同锁上同步:
- 线程 A 调用
obj1.doWork()→ 锁住obj1 - 线程 B 调用
obj2.doWork()→ 锁住obj2 - 两者完全不干扰,共享变量(尤其是 static 字段)就会出现竞态
解决方向很明确:要么改用 static synchronized 方法(锁 Class 对象),要么统一使用同一个锁对象(如 private static final Object LOCK = new Object();)。
锁对象被外部修改或重赋值
有些代码会把锁对象设计成可变字段,例如:
private Object lockObj = new Object(); // ……某处又写了 lockObj = new Object(); // ⚠️ 危险!旧锁失效,新锁无人争抢
或者在多线程环境下,多个地方对同一个锁变量进行重新赋值,导致各线程看到的锁对象不一致。只要锁变量不是 final 且未加 volatile 保护(通常也不该 volatile),就存在可见性与一致性风险。
锁对象生命周期短于临界区:局部变量作锁
把锁对象定义在方法内部,比如:
public void handle() {
Object localLock = new Object();
synchronized(localLock) {
// ……
}
}
每次调用都新建一个对象,每个线程锁的都是独一无二的“一次性锁”,自然无法互斥。锁对象必须是**所有竞争线程能共同访问到的同一个实例**,通常是静态常量、单例成员或传入的共享对象。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











