synchronized底层原理、和lock的区别
作为Java程序员,咱们在面试中十有八九会被问到多线程同步的问题。synchronized和Lock这两个关键词出现的频率极高,今天咱们就深入聊聊它们的底层原理和核心区别,帮你面试时对答如流!
👉2025年Java面试宝典抢先看:
链接: https://pan.baidu.com/s/1RUVf75gmDVsg8MQp4yRChg?pwd=9b3g 提取码: 9b3g
synchronized 的底层原理是啥?
synchronized是Java内置的锁机制,由JVM直接实现和管理。它的工作流程可以简单理解为三个阶段:
- 尝试获取锁: 当线程进入
synchronized代码块(或方法)时,JVM会尝试去获取这个对象关联的监视器锁(Monitor Lock),也叫对象锁。 - 锁升级过程:
- 无锁状态: 对象刚创建,没有任何线程持有锁。
- 偏向锁: 如果只有一个线程反复访问该同步块,JVM为了减少加锁解锁的开销,会启用偏向锁。它标记这个锁“偏向”于该线程,后续该线程进入同步块几乎不需要任何额外操作(就像贴了个“此位专属于张三”的标签)。
- 轻量级锁(自旋锁): 当有第二个线程尝试获取锁(发生锁竞争),偏向锁就失效了。此时升级为轻量级锁。JVM会在当前线程的栈帧中创建一个锁记录(Lock Record)空间,然后尝试使用CAS操作(Compare and Swap,一种乐观锁机制)将对象头中的Mark Word复制到锁记录中,并尝试将对象头的Mark Word替换为指向锁记录的指针。如果替换成功,当前线程就获得了轻量级锁。如果失败,说明有竞争,线程会进行一段时间的自旋(空循环等待),尝试再次获取锁。
- 重量级锁: 如果自旋等待也失败了(或者自旋次数达到阈值),锁就会膨胀为重量级锁。此时,未被获取到锁的线程会被挂起(进入阻塞状态),放入一个等待队列中,等待持有锁的线程释放锁后被操作系统唤醒。这个挂起和唤醒操作涉及到用户态到内核态的切换,开销比较大。
- 释放锁: 持有锁的线程执行完同步块后,会释放对象的监视器锁。如果是重量级锁,JVM会唤醒等待队列中的一个(或多个)线程去竞争锁。

图:synchronized锁升级过程示意图
核心要点:
synchronized锁是JVM内置的,基于对象头中的Mark Word实现锁状态标记和锁升级。- 锁升级路径是:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。目标是尽可能减少在无竞争或低竞争情况下的开销。
- 重量级锁依赖于操作系统的**互斥量(Mutex)**进行线程阻塞和唤醒,开销最大。
- 它是非公平锁(默认情况下,不保证等待队列里线程的获取顺序)。
Lock接口又是咋回事?
Lock是Java 5在java.util.concurrent.locks包下引入的显式锁接口(比如ReentrantLock就是它的一个实现)。它提供了比synchronized更灵活、更强大的锁操作。
- 显式加锁解锁: 使用
Lock需要手动调用lock()方法获取锁,并在finally块中调用unlock()方法释放锁。这种显式控制避免了synchronized隐式加解锁可能带来的作用域问题。 - 实现原理: 以常用的
ReentrantLock为例,它的核心是依赖一个同步器(Sync),这个同步器内部维护了一个状态变量(state)和一个CLH队列(或类似的双向链表结构)来管理等待线程。它大量使用了CAS操作(Compare and Set)来保证原子性地修改状态和操作队列。当线程尝试获取锁(lock())时:- 如果
state == 0(表示锁空闲),则尝试用CAS操作将state设置为1(或当前线程的重入次数),成功则获取锁。 - 如果锁已被占用(
state > 0),则检查持有者是否是当前线程(可重入性),是则state++。 - 如果不是,则线程会被封装成一个节点(Node)加入到CLH等待队列尾部,并可能进入自旋或阻塞等待状态(具体策略可配置)。队列的管理和线程的阻塞/唤醒也是由Lock的实现类(如AQS的子类)自己控制。
- 如果
- 公平性可选:
ReentrantLock可以在构造时指定是公平锁(严格按照等待队列的FIFO顺序获取锁)还是非公平锁(允许插队,性能通常更高)。 - 条件变量支持: 通过
newCondition()方法可以创建多个Condition对象,实现更精细的线程等待/通知机制(await(),signal(),signalAll()),比synchronized的wait()/notify()更灵活。

图:Lock接口使用示例
synchronized 和 Lock 的核心区别是啥?
| 特性 | synchronized | Lock (以 ReentrantLock 为例) |
| :------------------ | :------------------------------------------- | :------------------------------------------- |
| 实现层面 | JVM 内置关键字,由 JVM 实现和管理 | Java API 接口,由 JDK 类库实现 (如 AQS) |
| 获取/释放方式 | 隐式:自动进入同步块获取,退出块释放 | 显式:需手动调用 lock() 获取,unlock() 释放(必须放在 finally 块) |
| 锁状态 | 无法直接判断 | 可通过 tryLock() 尝试获取或判断锁状态 |
| 锁类型 | 非公平锁 (不可选择) | 可灵活选择:公平锁 or 非公平锁 |
| 中断响应 | 阻塞时无法响应中断 | 阻塞时可响应中断 (lockInterruptibly()) |
| 超时获取 | 不支持 | 支持 (tryLock(long time, TimeUnit unit)) |
| 绑定条件 | 一个对象只能关联一个内置的条件队列 (wait/notify) | 一个锁可以创建多个 Condition 对象 |
| 可重入性 | 支持 | 支持 |
| 性能 (低竞争) | 偏向锁、轻量级锁下性能较好 | 可能略低于优化后的 synchronized |
| 性能 (高竞争) | 膨胀为重量级锁后性能下降 | 通常比重量级 synchronized 表现更好 |
| 锁释放 | 自动释放 | 必须手动释放,否则可能导致死锁 |
| 代码灵活性 | 相对简单但不够灵活 | 高度灵活,可定制化强 |
面试回答重点提炼:
- 实现不同:
synchronized是JVM亲儿子(内置锁),Lock是JDK实现的API。 - 使用方式:
synchronized自动加解锁,省心但有局限;Lock手动lock/unlock,灵活但易忘(必须配finally)。 - 功能特性:
Lock完胜!它能响应中断、支持超时、能选公平/非公平、还能搞多个条件队列。这些都是synchronized做不到的。 - 性能差异: 低竞争时
synchronized(尤其偏向锁、轻量级锁)可能更快;高竞争时,精心调优的Lock(特别是非公平锁)通常性能更好,因为它避免了重量级锁频繁的上下文切换。 - 选择原则:
- 需要简单同步,代码块范围清晰,用
synchronized更简洁。 - 需要高级功能(可中断、超时、公平性、多个条件)、或需要更好的高并发性能、或锁操作需要跨越多个方法/代码块时,选
Lock。
- 需要简单同步,代码块范围清晰,用
实战面试怎么答?
面试官:“说说synchronized和Lock的区别?”
参考答案:
“好的。synchronized是Java的关键字,由JVM底层实现锁的获取和释放,使用起来比较方便,会自动加锁解锁。它的锁机制有个优化过程叫锁升级,会从无锁到偏向锁,再到轻量级锁(自旋),最后到重量级锁,这个升级过程是为了在无竞争或低竞争时减少开销。不过synchronized默认是非公平锁,而且功能上有些限制,比如它不能响应中断、不支持超时获取锁、一个对象只能关联一个等待/通知队列。
而Lock是一个接口,比如常用的ReentrantLock。它需要开发者显式地调用lock()和unlock()方法来


