问 synchronized,很多人第一反应是“重量级锁”。这个印象太旧,也太容易把话题带偏。现代 JVM 对锁做过不少优化,不同版本对偏向锁等机制的处理也有变化。面试里更应该先回答一个更朴素的问题:这把锁到底保护了什么共享状态。
并发安全不是给方法加一个关键字就结束。锁的价值在于把一段对共享数据的复合操作变成临界区,让同一时刻只有一个线程进入,并且让前一个线程的修改对后一个进入同一把锁的线程可见。
synchronized 同时解决互斥和可见性
synchronized 编译后会围绕对象监视器进入和退出,方法级同步也是同一类语义。进入锁之前要获得对应对象的 Monitor,退出锁时释放。释放锁和后续获取同一把锁之间会建立可见性关系,所以它不只是“排队执行”,还会让共享变量的修改被正确发布。
这就是它和 volatile 的重要区别。volatile 适合状态标记和安全发布,但不能把多个步骤合成一个不可打断的整体。synchronized 则可以保护一段业务不变量,比如余额扣减、库存检查加扣减、状态流转校验加更新。
锁对象选错,比锁慢更危险
初学者容易在方法上直接加 synchronized,却没想清锁的是 this、Class 对象,还是某个专门的私有锁对象。锁范围太大,会让无关操作互相等待;锁范围太小,又保护不住共享状态。
更糟的是锁住 String 字面量、Integer 缓存对象、公开可访问的对象。外部代码可能拿到同一把锁,造成难以解释的阻塞。比较稳妥的做法是用 private final Object 作为内部锁,或者把共享状态封装在一个更小的对象里,让锁和被保护的数据靠在一起。
临界区里不要放慢动作
锁竞争严重时,问题常常不是 synchronized 本身,而是临界区里做了慢操作:远程调用、数据库查询、文件 IO、大对象序列化、复杂计算。锁持有时间越长,其他线程等待越久,吞吐就会下降。
项目排查时可以看线程栈、锁等待、接口耗时和业务路径。优化也不一定是换成 ReentrantLock 或无锁结构,很多时候是缩短临界区:先在锁外准备数据,锁内只做状态校验和更新;或者把全局锁拆成按用户、订单、资源维度的细粒度锁。
锁优化不是背术语比赛
轻量级锁、锁粗化、锁消除这些概念有价值,但面试里不要把它们背成固定流程。JVM 会根据运行情况做优化,具体行为还和版本、参数、代码形态有关。后端开发更重要的是写出优化器能理解、业务也能维护的同步代码。
如果共享状态本身可以避免,就不要制造锁。不可变对象、线程封闭、消息队列串行化、数据库唯一约束、CAS 原子类,都可能比一把大锁更合适。但这些选择都有边界:CAS 适合简单变量,数据库约束适合最终落库的不变量,串行队列适合按 key 排队的任务。
面试里该怎么收尾
一个成熟回答不是说 synchronized 已经过时,也不是说它万能。可以这样讲:synchronized 提供互斥和可见性,适合保护清晰的临界区;关键是选对锁对象、控制锁粒度、避免慢操作进入锁内;如果竞争严重,再结合业务拆锁、原子类、不可变设计或串行化队列。这样回答,才像真正处理过并发问题的人。