Appearance
synchronized 为什么仍然是 Java 并发里的默认首选
synchronized 这几年经常被初学者误解成“老旧、重量级、性能差”的方案,于是很多人一上来就想用 Lock、CAS、无锁容器把代码写得很“高级”。
但在真实业务里,synchronized 依然是最稳、最省心、综合成本最低的默认互斥方案之一。它的问题往往不在锁本身,而在我们没有先分清场景。
先说结论
- 普通对象级互斥、短临界区、可读性优先的业务代码,优先用
synchronized synchronized不是一直都“重量级”,JVM 会根据竞争情况做偏向、轻量级、自旋到重量级的升级- 真正该重点优化的通常不是“换锁”,而是缩短临界区、减少共享状态、避免锁内 IO
- 只有当你需要可中断、可超时、条件队列、公平锁等能力时,才优先考虑
ReentrantLock
它到底解决什么问题
synchronized 解决的是多线程访问共享状态时的互斥和可见性问题:
- 同一时刻只允许一个线程进入临界区
- 退出同步块时,把工作内存中的修改刷新到主内存
- 进入同步块时,读到的是加锁释放后对共享变量的最新结果
对业务代码来说,这已经覆盖了大量需求,比如:
- 库存扣减前的本地内存状态保护
- 单机内对某个 Map、List、计数器的互斥更新
- 订单状态机在 JVM 内的串行推进
为什么它仍然是默认首选
1. 语义最直接
synchronized 是 Java 语言层面的能力,不需要开发者额外维护加锁和解锁配对。
java
public synchronized void addItem(Item item) {
items.add(item);
total++;
}相比显式锁,它天然少一个风险点: 忘记 unlock()。
2. 异常安全
同步块退出时,JVM 会自动释放锁。
这意味着它对业务代码更加宽容,尤其适合新团队和高频维护代码。
3. JVM 已经替你做了很多优化
现代 JVM 并不是一上来就走重量级互斥。一般会经历这样一条路径:
- 无竞争: 直接进入
- 轻度竞争: CAS + 自旋
- 竞争加剧: 膨胀成重量级监视器锁
所以很多“synchronized 性能差”的说法,放到现在已经不准确了。
锁升级到底在升级什么
可以把它理解为 JVM 在动态选择更合适的同步成本。
1. 无锁或偏向状态
没有竞争时,对象头里会记录偏向线程信息。
适合同一线程反复进入同一个同步块的情况。
2. 轻量级锁
多个线程偶尔竞争时,JVM 会优先尝试 CAS 抢占锁记录。
这时线程不一定立刻阻塞,往往先自旋等待。
3. 重量级锁
竞争激烈、自旋无效时,锁会膨胀到重量级监视器,线程进入阻塞和唤醒流程。
这时会涉及用户态和内核态切换,成本就上来了。
重点不是记这几个名词,而是知道:
- 低竞争下,
synchronized完全可以很轻 - 真正的性能问题通常来自高竞争,而不是关键字本身
什么时候它特别合适
下面这些场景,synchronized 往往是非常好的第一选择:
- 方法级或对象级的简单互斥
- 临界区很短,只做内存计算,不做外部调用
- 团队更看重代码稳定性和可维护性
- 并发量不算夸张,或者热点竞争点不多
典型例子:
java
public void submit(OrderCommand command) {
synchronized (this) {
if (closed) {
throw new IllegalStateException("queue closed");
}
queue.add(command);
}
}这类代码没有必要为了“高级感”改成复杂的 CAS 结构。
什么时候不该死用 synchronized
1. 需要可中断锁等待
比如线程等待期间需要响应取消,就该考虑 lockInterruptibly()。
2. 需要超时获取锁
比如抢锁失败要快速降级,而不是无限等待,tryLock() 更合适。
3. 需要多个条件队列
生产者消费者模型里,一个锁下可能需要多个条件队列,这时 Condition 更灵活。
4. 锁内存在慢操作
如果锁内有数据库、RPC、文件 IO,再好的锁都会出问题。
这里该优化的是临界区边界,而不是锁类型。
工程里真正的高频坑
1. 锁范围过大
把查询、拼装、远程调用都放进同步块,锁竞争一定会迅速恶化。
2. 锁对象选错
比如用字符串常量、自动装箱对象做锁,容易出现意外共享。
3. 共享状态设计过粗
很多人觉得是锁慢,实际上是一个全局对象承载了太多互斥职责。
更好的思路通常是:
- 按用户、订单、分片拆分锁粒度
- 尽量减少可变共享状态
- 读多写少场景改成更合适的数据结构
面试和生产都能用的一句话判断
如果你面对的是 JVM 内简单互斥问题,先问自己三件事:
- 临界区是否足够短
- 是否需要超时、中断、条件队列等额外能力
- 是否真的存在高竞争
如果前两项答案都是否,第三项又不严重,那 synchronized 大概率就是最合适的默认方案。
总结
synchronized 到今天依然重要,不是因为它“传统”,而是因为它在大量普通业务场景里依然是成本最低、语义最清晰、最不容易写错的同步方式。
真正值得优化的方向,不是盲目把它替换掉,而是把锁粒度、共享状态和临界区边界设计清楚。