正确使用对象锁实现互斥的关键是锁同一对象、精准控制临界区、利用其可见性与可重入性,并避免用字符串等不可控对象作锁。

正确使用对象锁实现互斥,核心在于明确“锁谁”“锁哪段”“是否共用同一把锁”。synchronized 的本质是基于对象的监视器(Monitor),只有多个线程竞争**同一个对象锁**时,互斥才真正生效;若锁对象不同,哪怕代码逻辑一致,也形同虚设。
一、锁对象必须一致,否则互斥失效
对象锁的作用范围由锁引用的对象实例决定。常见错误是误以为“方法加了 synchronized 就自动互斥”,其实:
- 修饰非静态方法 → 锁的是 this(当前实例)
- 修饰静态方法 → 锁的是 当前类的 Class 对象(如 Phone.class)
- 修饰同步块 → 锁的是括号中明确指定的对象(如
synchronized(obj))
例如两个线程分别操作 new Phone() 创建的不同实例,即使都调用同一个 synchronized 实例方法,也不会互相阻塞——因为锁的是各自不同的 this。要让它们互斥,就得显式共用一个锁对象(比如 static final Object lock = new Object();)。
二、临界区要精准,避免锁粒度过大或过小
只把真正需要保护的共享资源访问代码包进 synchronized 块,其余无关逻辑(如日志打印、IO 等)应放在锁外:
- 锁范围太大 → 串行化严重,吞吐量下降(比如整个方法都加锁,但只有一行改共享变量)
- 锁范围太小 → 可能漏掉关键操作,破坏原子性(比如先读再写却没包在一起)
推荐优先使用同步代码块而非同步方法,便于控制粒度。例如:
✅ 推荐(只锁关键赋值):void updateBalance(double delta) {<br> synchronized(balanceLock) {<br> balance += delta;<br> }<br>}
❌ 避免(整个方法锁,含无关逻辑):
public synchronized void updateBalance(double delta) {<br> log.debug("updating...");<br> balance += delta;<br> notifyObservers(); // 不涉及共享状态,不该被锁住<br>}
三、注意锁的可见性与可重入性
synchronized 不仅保证互斥,还天然提供内存可见性:进入同步块前会刷新本地内存,退出时强制写回主内存。这意味着无需额外 volatile 修饰被保护的变量。
它也是可重入锁:同一线程可多次获取同一把锁(计数器递增),不会死锁。例如在 synchronized 方法内调用另一个该对象的 synchronized 方法,能正常执行。
但要注意:不可跨对象重入。如果方法 A 锁 this,内部调用另一个对象 obj 的 synchronized 方法,那就要获取 obj 的锁——这是另一把锁,需另行竞争。
四、慎用 String 或 intern 字符串作锁对象
以下写法极危险:
synchronized("LOCK") { ... }因为字符串常量池机制,"LOCK" 可能在别处被其他模块无意复用,导致意外的锁竞争甚至死锁。同样,避免用 Integer、Boolean 等缓存对象作锁。
安全做法是声明私有、final、static 的 Object 实例作为锁:
private static final Object balanceLock = new Object();确保锁对象唯一、可控、不对外暴露。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











