真实面经题目 · 原创解析
Java 有哪些常见加锁机制?
Java 常见加锁机制包括 synchronized、ReentrantLock、ReadWriteLock、StampedLock、原子类和基于 AQS 的同步器。面试重点不是罗列名称,而是说明它们的互斥语义、可重入性、公平性、可中断、条件等待、读写并发和适用场景。
真实面经题目 · 原创解析
Java 常见加锁机制包括 synchronized、ReentrantLock、ReadWriteLock、StampedLock、原子类和基于 AQS 的同步器。面试重点不是罗列名称,而是说明它们的互斥语义、可重入性、公平性、可中断、条件等待、读写并发和适用场景。
可以从内置锁和显式锁两大类回答。synchronized 是 JVM 内置监视器锁,进入同步代码块或方法时自动加锁,退出或异常时自动释放,支持可重入和 wait/notify;ReentrantLock 是 JUC 显式锁,支持可重入、tryLock、可中断等待、公平锁选项和多个 Condition;ReadWriteLock 把读锁和写锁分开,适合读多写少场景,允许多个读线程并发但写线程互斥;StampedLock 提供乐观读、悲观读和写锁,性能潜力高但不可重入且使用更复杂;Atomic 类和 CAS 适合简单状态更新,避免阻塞但要处理自旋和 ABA 等问题。实际选择要看临界区大小、竞争强度、读写比例、是否需要超时和中断、是否需要条件队列以及代码可维护性。
synchronized 是 Java 语言级同步机制,可以修饰实例方法、静态方法或代码块。它基于对象监视器实现互斥,同一线程可以重入,退出同步块或抛异常时自动释放锁。优点是语义简单、不会忘记 unlock,JVM 对它有大量优化;限制是灵活性较弱,不能显式尝试获取、不能设置公平性,也不能像 ReentrantLock 那样拥有多个条件队列。
ReentrantLock 是 JUC 中最常用的显式互斥锁,底层依赖 AQS。它和 synchronized 一样可重入,但提供更多控制能力,例如 tryLock 立即失败、带超时等待、lockInterruptibly 响应中断、公平锁或非公平锁选择,以及多个 Condition。代价是必须在 finally 中 unlock,否则异常路径容易造成死锁。它适合需要可中断、限时等待或复杂条件队列的场景。
ReadWriteLock 典型实现是 ReentrantReadWriteLock,读锁共享、写锁独占。多个读线程可以同时进入,写线程进入时需要排斥其他读写线程,适合读多写少、读操作耗时明显且写操作不频繁的共享数据结构。它并不总是更快,如果写多、读临界区很短或锁竞争不明显,读写锁的维护成本可能超过收益。还要注意锁降级、写锁饥饿和缓存一致性问题。
StampedLock 提供写锁、悲观读锁和乐观读。乐观读不会阻塞写线程,而是先读取数据,再用 stamp 校验期间是否发生写入;如果校验失败,再退化为悲观读锁。它适合读非常多、写较少且读取可重试的场景。但 StampedLock 不可重入,使用不当容易死锁;stamp 需要正确保存和校验,代码复杂度高,因此更适合性能敏感且团队能驾驭的局部模块。
AtomicInteger、AtomicReference、LongAdder 等基于 CAS 或分段累加思想,适合简单计数、状态切换和引用更新。它们不是传统阻塞锁,但能在低到中等竞争下减少上下文切换。JUC 里还有 Semaphore、CountDownLatch、CyclicBarrier、Phaser 等同步器,它们解决的是许可控制、等待多个任务完成或阶段协作,不一定是互斥锁。选择机制时要先明确是在保护临界区、协调线程,还是做无锁状态更新。
普通互斥优先 synchronized,语义简单且自动释放。需要 tryLock、超时、可中断、公平性或多个 Condition 时,再选择 ReentrantLock。
不一定。只有读多写少、读操作耗时较明显、竞争存在时才可能收益明显。写多或临界区很短时,读写锁的管理成本可能更高。
CAS 不是传统阻塞锁,它通过原子比较交换更新共享状态,失败时通常重试。它适合简单状态更新,但高竞争下会自旋消耗 CPU,也要注意 ABA 等问题。
wait/notify 依赖 synchronized 的对象监视器,一个对象对应一个等待集合;Condition 依赖 Lock,一个 Lock 可以创建多个 Condition,使不同等待条件分队列管理。