面试鸭返利网

synchronized底层原理、和lock的区别

深入解析Java多线程同步:synchronized与Lock底层原理对比。synchronized作为JVM内置锁,通过锁升级机制(无锁→偏向锁→轻量级锁→重量级锁)优化性能,但功能受限;而Lock接口(如ReentrantLock)提供更灵活的显式锁控制,支持可中断、超时获取、公平锁等高级特性。本文详细剖析两者实现原理、性能差异及适用场景,助你掌握Java并发编程核心知识点,提升面试通过率。适合Java开发者、面试准备者及对多线程同步机制感兴趣的技术人员阅读。

synchronized底层原理、和lock的区别

作为Java程序员,咱们在面试中十有八九会被问到多线程同步的问题。synchronizedLock这两个关键词出现的频率极高,今天咱们就深入聊聊它们的底层原理和核心区别,帮你面试时对答如流!

👉2025年Java面试宝典抢先看
链接: https://pan.baidu.com/s/1RUVf75gmDVsg8MQp4yRChg?pwd=9b3g 提取码: 9b3g


synchronized 的底层原理是啥?

synchronized是Java内置的锁机制,由JVM直接实现和管理。它的工作流程可以简单理解为三个阶段:

  1. 尝试获取锁: 当线程进入synchronized代码块(或方法)时,JVM会尝试去获取这个对象关联的监视器锁(Monitor Lock),也叫对象锁。
  2. 锁升级过程:
    • 无锁状态: 对象刚创建,没有任何线程持有锁。
    • 偏向锁: 如果只有一个线程反复访问该同步块,JVM为了减少加锁解锁的开销,会启用偏向锁。它标记这个锁“偏向”于该线程,后续该线程进入同步块几乎不需要任何额外操作(就像贴了个“此位专属于张三”的标签)。
    • 轻量级锁(自旋锁): 当有第二个线程尝试获取锁(发生锁竞争),偏向锁就失效了。此时升级为轻量级锁。JVM会在当前线程的栈帧中创建一个锁记录(Lock Record)空间,然后尝试使用CAS操作(Compare and Swap,一种乐观锁机制)将对象头中的Mark Word复制到锁记录中,并尝试将对象头的Mark Word替换为指向锁记录的指针。如果替换成功,当前线程就获得了轻量级锁。如果失败,说明有竞争,线程会进行一段时间的自旋(空循环等待),尝试再次获取锁。
    • 重量级锁: 如果自旋等待也失败了(或者自旋次数达到阈值),锁就会膨胀为重量级锁。此时,未被获取到锁的线程会被挂起(进入阻塞状态),放入一个等待队列中,等待持有锁的线程释放锁后被操作系统唤醒。这个挂起和唤醒操作涉及到用户态到内核态的切换,开销比较大。
  3. 释放锁: 持有锁的线程执行完同步块后,会释放对象的监视器锁。如果是重量级锁,JVM会唤醒等待队列中的一个(或多个)线程去竞争锁。

面试鸭返利网
图:synchronized锁升级过程示意图

核心要点:

  • synchronized锁是JVM内置的,基于对象头中的Mark Word实现锁状态标记和锁升级。
  • 锁升级路径是:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。目标是尽可能减少在无竞争或低竞争情况下的开销。
  • 重量级锁依赖于操作系统的**互斥量(Mutex)**进行线程阻塞和唤醒,开销最大。
  • 它是非公平锁(默认情况下,不保证等待队列里线程的获取顺序)。

Lock接口又是咋回事?

Lock是Java 5在java.util.concurrent.locks包下引入的显式锁接口(比如ReentrantLock就是它的一个实现)。它提供了比synchronized更灵活、更强大的锁操作。

  1. 显式加锁解锁: 使用Lock需要手动调用lock()方法获取锁,并在finally块中调用unlock()方法释放锁。这种显式控制避免了synchronized隐式加解锁可能带来的作用域问题。
  2. 实现原理: 以常用的ReentrantLock为例,它的核心是依赖一个同步器(Sync),这个同步器内部维护了一个状态变量(state)和一个CLH队列(或类似的双向链表结构)来管理等待线程。它大量使用了CAS操作(Compare and Set)来保证原子性地修改状态和操作队列。当线程尝试获取锁(lock())时:
    • 如果state == 0(表示锁空闲),则尝试用CAS操作将state设置为1(或当前线程的重入次数),成功则获取锁。
    • 如果锁已被占用(state > 0),则检查持有者是否是当前线程(可重入性),是则state++
    • 如果不是,则线程会被封装成一个节点(Node)加入到CLH等待队列尾部,并可能进入自旋或阻塞等待状态(具体策略可配置)。队列的管理和线程的阻塞/唤醒也是由Lock的实现类(如AQS的子类)自己控制。
  3. 公平性可选: ReentrantLock可以在构造时指定是公平锁(严格按照等待队列的FIFO顺序获取锁)还是非公平锁(允许插队,性能通常更高)。
  4. 条件变量支持: 通过newCondition()方法可以创建多个Condition对象,实现更精细的线程等待/通知机制(await(), signal(), signalAll()),比synchronizedwait()/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 表现更好 | | 锁释放 | 自动释放 | 必须手动释放,否则可能导致死锁 | | 代码灵活性 | 相对简单但不够灵活 | 高度灵活,可定制化强 |

面试回答重点提炼:

  1. 实现不同: synchronized是JVM亲儿子(内置锁),Lock是JDK实现的API。
  2. 使用方式: synchronized自动加解锁,省心但有局限;Lock手动lock/unlock,灵活但易忘(必须配finally)。
  3. 功能特性: Lock完胜!它能响应中断、支持超时、能选公平/非公平、还能搞多个条件队列。这些都是synchronized做不到的。
  4. 性能差异: 低竞争时synchronized(尤其偏向锁、轻量级锁)可能更快;高竞争时,精心调优的Lock(特别是非公平锁)通常性能更好,因为它避免了重量级锁频繁的上下文切换。
  5. 选择原则:
    • 需要简单同步,代码块范围清晰,用synchronized更简洁。
    • 需要高级功能(可中断、超时、公平性、多个条件)、或需要更好的高并发性能、或锁操作需要跨越多个方法/代码块时,选Lock

实战面试怎么答?

面试官:“说说synchronizedLock的区别?”

参考答案:

“好的。synchronized是Java的关键字,由JVM底层实现锁的获取和释放,使用起来比较方便,会自动加锁解锁。它的锁机制有个优化过程叫锁升级,会从无锁到偏向锁,再到轻量级锁(自旋),最后到重量级锁,这个升级过程是为了在无竞争或低竞争时减少开销。不过synchronized默认是非公平锁,而且功能上有些限制,比如它不能响应中断、不支持超时获取锁、一个对象只能关联一个等待/通知队列。

Lock是一个接口,比如常用的ReentrantLock。它需要开发者显式地调用lock()unlock()方法来

如果你想获取更多关于面试鸭的优惠信息,可以访问面试鸭返利网面试鸭优惠网,了解最新的优惠活动和返利政策。

立即加入面试鸭会员 →