同步方法和同步代码块无绝对优劣,关键在于锁粒度匹配临界资源范围:同步方法锁整个实例或类,易致假竞争与性能下降;同步代码块可指定锁对象、精准控制作用域,降低粒度、提升并发,但需避免锁对象误用与生命周期失控。

同步方法和同步代码块没有绝对优劣,选择取决于你真正需要保护的临界资源范围。用错会导致性能下降甚至死锁,用对才能兼顾线程安全与吞吐量。
同步方法会锁住整个实例或类,粒度太粗
声明为 synchronized 的实例方法,等价于用 this 作锁;静态方法则用当前类的 Class 对象作锁。这意味着:即使方法内部只有几行代码访问共享变量,整个方法执行期间其他线程都无法进入该对象的任意其他同步方法。
- 多个无关的同步方法共用一把锁,形成“假竞争”——比如
getCount()和saveToFile()都是同步方法,但后者耗时长且不操作计数器,却阻塞了前者 - 无法对不同资源使用不同锁,比如一个对象同时管理缓存和日志,二者本可并行,但同步方法强制串行
- 若方法中调用了外部服务(如数据库、HTTP),锁持有时间被意外拉长,严重拖慢并发能力
同步代码块可精准控制锁对象和作用域
用 synchronized(obj) { ... } 显式指定锁对象,并只包裹真正需要互斥的代码段。这是降低锁粒度最直接的方式。
- 锁对象可以是私有
final Object lock = new Object();,避免被外部误用或干扰 - 多个不相关的临界区可用不同锁对象隔离,例如缓存用
cacheLock,计数器用countLock - 能避开非临界逻辑(如日志、参数校验、IO),让锁持有时间尽可能短
- 注意:锁对象不能是
this、String、Integer等可能被外部获取或复用的对象,否则破坏封装性
常见误用场景与修复示例
下面这段代码看似安全,实则存在隐性锁竞争:
立即学习“Java免费学习笔记(深入)”;
public class Counter {
private int count = 0;
private final Object lock = new Object();
// ❌ 错误:仍用同步方法,没利用已有锁对象
public synchronized void increment() {
count++;
}
// ✅ 正确:仅同步修改 count 的部分,且复用专用锁
public void increment() {
synchronized(lock) {
count++;
}
}
}
另一个典型问题是锁对象生命周期失控:
- 用
new Object()作为锁,但每次调用都新建——等于没锁 - 用
list.size()这类返回基本类型的表达式作锁对象(编译不过,但有人会写synchronized(list.size()),实际是锁了一个临时Integer) - 在循环内反复创建锁对象,导致每次同步块都用不同锁,完全失效
性能与可维护性的实际权衡点
同步代码块不是银弹。过度拆分也会增加理解成本和出错概率:
- 当整个方法逻辑天然原子、且无外部依赖时,同步方法反而更清晰(比如一个纯内存计数器的
getAndIncrement()) - 若临界区极小(如单个
++),考虑用AtomicInteger替代锁,避免 JVM 同步开销 - 嵌套同步块要警惕锁顺序,否则容易引发死锁;而同步方法因锁对象固定,相对更可控
- 使用
ReentrantLock可以实现超时、中断、多条件等待等高级控制,但代价是必须显式lock()/unlock(),遗漏unlock()会导致永久阻塞
真正关键的不是语法选哪个,而是想清楚:哪几行代码必须原子执行?哪些变量会被多个线程同时读写?锁的生命周期是否和它保护的数据一致?这些问题比“用方法还是代码块”重要得多。










