一、核心概念与分类(基础必问)
从不同维度对锁进行分类。
1. 乐观锁 vs 悲观锁(思想层面)
- 悲观锁 :认为并发冲突一定会发生 ,因此在操作数据前必须先加锁。典型代表是
synchronized关键字和ReentrantLock。- 类比:假设所有人都会修改数据,所以每次进屋(操作数据)都先锁门。
- 乐观锁 :认为并发冲突不常发生 ,因此先直接操作数据,在提交更新时再判断是否有冲突。通常通过版本号 或CAS机制 实现。
- 典型实现 :数据库的
version字段,Java中的AtomicInteger(基于CAS)。 - 类比:假设大家很少冲突,所以先干事,提交前看一眼有没有人动过(检查版本号)。
- 典型实现 :数据库的
2. 公平锁 vs 非公平锁(调度策略)
- 公平锁 :多个线程按照申请锁的顺序 来获取锁,先到先得。
ReentrantLock(true)。- 优点:不会"饿死"。
- 缺点:吞吐量较低,需要维护一个队列。
- 非公平锁 :允许"插队",线程尝试获取锁时,如果锁刚好可用,则直接获取,不管队列里是否有等待者。
synchronized和ReentrantLock()(默认)。- 优点:吞吐量高,减少线程切换开销。
- 缺点:可能导致某些线程长时间"饿死"。
3. 可重入锁 vs 非可重入锁(能否重复进入)
- 可重入锁(递归锁) :同一个线程在外层方法获取锁后,在进入内层方法时会自动获取该锁(锁的计数器+1)。
synchronized和ReentrantLock都是可重入的。- 优点:避免死锁,方便递归和嵌套调用。
- 示例 :
synchronized方法A调用另一个synchronized方法B。
- 非可重入锁:与之相反,自己锁了自己就不能再进了。
4. 独享锁(排他锁) vs 共享锁(读写锁)
- 独享锁 :一次只能被一个线程持有。
synchronized、ReentrantLock是独享锁。 - 共享锁 :允许多个线程同时持有。典型代表是
ReentrantReadWriteLock.ReadLock。- 读写锁 :
ReentrantReadWriteLock是ReadLock(共享)和WriteLock(独享)的组合,遵循 "读读共享、读写互斥、写写互斥" 的原则,能极大提升读多写少场景的性能。 - 升级 :JDK 8 引入了性能更好的
StampedLock,提供了乐观读、读写锁转换等更灵活的功能。
- 读写锁 :
5. 自旋锁 vs 互斥锁
自旋锁: 当没拿到锁时, 不会释放CPU, 而是一直处于活跃状态(while循环), 一直循环判断锁是否被释放. 循环时间一长十分消耗CPU资源.
互斥锁: 没拿到锁就释放CPU, 进入阻塞等待状态, 直到被唤醒.
二、关键锁的实现与原理(进阶高频)
1. synchronized 关键字
- 用法:修饰实例方法、静态方法、代码块。
- 原理:JVM 层面实现。
- 锁升级过程(重点!) :为了在性能和开销间取得平衡,JDK 1.6 后引入了"偏向锁 -> 轻量级锁 -> 重量级锁"的升级过程,锁只能升级不能降级。
- 无锁:新对象。
- 偏向锁:假设只有一个线程访问,会在对象头Mark Word记录线程ID。后续该线程进入/退出同步块只需要检查ID,无需CAS。
- 轻量级锁:当有第二个线程竞争时,升级为轻量级锁。通过CAS自旋(适应性自旋)尝试获取锁,避免直接进入内核态阻塞。
- 重量级锁 :自旋失败或竞争激烈时,升级为重量级锁(操作系统层面的互斥量
Mutex),线程进入阻塞队列,性能消耗最大。
- 面试点 :
synchronized与ReentrantLock的区别。
先尝试偏向锁,降低无竞争的开销;出现竞争时,升级为轻量级锁,通过自旋避免阻塞;自旋失败(竞争加剧),最终升级为重量级锁,让线程阻塞。当 synchronized 升级为重量级锁时,它需要通过操作系统的互斥锁(Mutex Lock)来实现线程的阻塞和唤醒
2. ReentrantLock(AQS 的代表) - 核心 :基于
AbstractQueuedSynchronizer(AQS)实现。AQS 内部维护了一个volatile int state(同步状态)和一个 CLH 变体的FIFO双向队列(阻塞线程队列)。 - 原理 :
- 加锁 :通过
lock()->acquire()->tryAcquire()尝试获取锁(修改state从0到1),成功则独占;失败则通过addWaiter()将线程封装为Node加入队列尾部,并通过acquireQueued()进行自旋或阻塞。 - 解锁 :
unlock()->release()->tryRelease()将state减为0,并unparkSuccessor()唤醒队列中的下一个线程。
- 加锁 :通过
- 优势 :相比
synchronized,提供了更多功能:可中断 (lockInterruptibly())、可定时 (tryLock(timeout))、可设置公平/非公平 、支持多个条件变量 (Condition)。

3. CAS(Compare And Swap)与原子类
- CAS:乐观锁的核心实现,是一条CPU原子指令。操作包含三个值:内存位置 V、预期原值 A、新值 B。仅当 V == A 时,才将 V 更新为 B。
- 原子类 :
java.util.concurrent.atomic包下的类(如AtomicInteger)使用 CAS 实现无锁线程安全操作。 - 缺点 :
- ABA问题 :值从A变成B又变回A,CAS会认为没变。解决方案是使用
AtomicStampedReference(带版本戳)。 - 循环时间长开销大:自旋CAS如果长时间不成功,会消耗CPU。
- 只能保证一个共享变量的原子操作 。可以用
AtomicReference封装多个变量。
- ABA问题 :值从A变成B又变回A,CAS会认为没变。解决方案是使用