同步方法和同步代码块无绝对优劣,关键在于锁粒度匹配临界资源范围:同步方法锁整个实例或类,易致假竞争与性能下降;同步代码块可指定锁对象、精准控制作用域,降低粒度、提升并发,但需避免锁对象误用与生命周期失控。
同步方法和同步代码块没有绝对优劣,选择取决于你真正需要保护的临界资源范围。用错会导致性能下降甚至死锁,用对才能兼顾线程安全与吞吐量。
声明为 synchronized 的实例方法,等价于用 this 作锁;静态方法则用当前类的 Class 对象作锁。这意味着:即使方法内部只有几行代码访问共享变量,整个方法执行期间其他线程都无法进入该对象的任意其他同步方法。
getCount() 和 saveToFile() 都是同步方法,但后者耗时长且不操作计数器,却阻塞了前者用 synchronized(obj) { ... } 显式指定锁对象,并只包裹真正需要互斥的代码段。这是降低锁粒度最直接的方式。
final Object lock = new Object();,避免被外部误用或干扰cacheLock,计数器用 countLock
this、String、Integer 等可能被外部获取或复用的对象,否则破坏封装性
下面这段代码看似安全,实则存在隐性锁竞争:
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() 会导致永久阻塞真正关键的不是语法选哪个,而是想清楚:哪几行代码必须原子执行?哪些变量会被多个线程同时读写?锁的生命周期是否和它保护的数据一致?这些问题比“用方法还是代码块”重要得多。