Java锁分synchronized(JVM内置)和AQS显式锁两类;前者不可中断、无超时,后者支持公平性、中断、超时及多条件变量;读多写少场景优先StampedLock(轻量乐观读),复杂读逻辑选ReentrantReadWriteLock;偏向锁已弃用,锁粒度优化比换锁更重要。

Java里常见的锁类型及其底层实现机制
Java并发中的锁不是单一概念,而是分层实现的:最底层是JVM通过monitorenter/monitorexit指令支持的synchronized,属于JVM内置锁;上层是JUC包提供的显式锁,核心是AbstractQueuedSynchronizer(AQS)——它不直接提供锁,而是定义了状态管理、队列同步和等待/唤醒的模板,ReentrantLock、ReentrantReadWriteLock、StampedLock都基于它构建。
关键区别在于:synchronized无法中断等待、不支持超时、不暴露条件队列控制权;而AQS系锁可定制公平性、响应中断、设置超时、绑定多个Condition对象。
ReentrantLock vs synchronized:什么情况下必须换用显式锁
绝大多数场景下synchronized足够且更安全——JVM自动释放、无死锁风险(只要没在临界区调用wait()后忘记notify())、JIT还能做锁消除和粗化优化。只有以下情况才需切换到ReentrantLock:
- 需要响应线程中断:
lockInterruptibly(),比如任务执行中被Thread.interrupt()打断 - 需要尝试获取锁但不阻塞:
tryLock(),适合避免长时间等待的资源预占场景 - 需要带超时的获取:
tryLock(long, TimeUnit),防止无限期挂起 - 需要多个条件变量:
newCondition()返回独立的Condition,比synchronized+Object.wait()更灵活 - 需要显式控制锁的公平策略(虽然公平锁性能通常更差)
注意:ReentrantLock必须手动在finally块中调用unlock(),漏写会导致永久锁住资源。
立即学习“Java免费学习笔记(深入)”;
读多写少场景该选ReentrantReadWriteLock还是StampedLock
ReentrantReadWriteLock提供可重入的读写分离锁,读锁共享、写锁独占,适合读操作远多于写操作且读写逻辑清晰的场景。但它有个硬伤:写锁饥饿——持续有读请求时,写线程可能永远等不到机会。
StampedLock用乐观读+版本戳机制规避这个问题,典型模式是:
long stamp = lock.tryOptimisticRead();
int current = value;
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
current = value;
} finally {
lock.unlockRead(stamp);
}
}
但要注意:StampedLock不支持重入、不支持条件变量、写锁获取时会阻塞所有乐观读和悲观读,且validate()失败后若改用readLock(),必须确保后续不再调用validate()——否则可能误判为乐观读成功。
简单判断:如果读操作极轻量(比如只读一个int字段),优先用StampedLock;如果读逻辑复杂或需保证一致性视图,用ReentrantReadWriteLock更稳妥。
偏向锁、轻量级锁、重量级锁的升级路径与实际影响
这是synchronized在HotSpot JVM里的优化机制,仅存在于开启偏向锁的JDK 8(默认开启)及更早版本;JDK 15起已移除偏向锁。它的升级路径是:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。
偏向锁适用于单线程反复进入同一同步块的场景(如单例初始化),一旦有其他线程竞争,就会撤销偏向并升级;轻量级锁通过CAS操作避免系统调用,适合短临界区;重量级锁则真正挂起线程,依赖操作系统互斥量。
这些优化对开发者透明,但影响明显:高竞争下频繁锁升级反而增加开销;JDK 10后默认关闭偏向锁,JDK 15彻底删除,说明现代应用更倾向用ReentrantLock或无锁方案(如AtomicInteger、ConcurrentHashMap)替代传统锁。
真正容易被忽略的是:锁粒度。无论用哪种锁,如果把整个方法体甚至数据库事务包裹进去,再好的锁机制也救不了性能——先考虑能否缩小临界区,比换锁类型重要得多。











