Synchronized与ReentrantLock深入剖析:从表层到底层

287 阅读7分钟

Synchronized与ReentrantLock深入剖析:从表层到底层

在Java并发编程中,synchronizedReentrantLock是两种最常用的锁机制,它们在多线程环境下保障线程安全。面试官常常通过这两个锁作为切入点,考察候选人对Java并发机制(JUC)的理解深度。本文将从表层比较开始,逐步深入到锁的底层实现,剖析synchronized的锁升级、对象头Mark Word、互斥锁系统调用,以及ReentrantLock背后的AQS结构、模板方法和CAS机制,展示清晰的知识体系和逻辑脉络。


一、Synchronized与ReentrantLock的表层比较

1.1 基本概念

  • Synchronized:Java关键字,内置锁机制,基于JVM实现,适用于方法或代码块的同步。自动获取和释放锁,简单易用。
  • ReentrantLock:JUC包中的显式锁,基于AQS(AbstractQueuedSynchronizer)实现,提供更灵活的功能,如公平锁、条件变量等。

1.2 异同点比较

以下是两者在功能、性能和使用场景上的对比:

特性SynchronizedReentrantLock
锁类型内置锁(JVM级别)显式锁(Java API级别)
可重入性支持(同一个线程可多次获取锁)支持(基于计数器实现)
公平性非公平锁(可能导致线程饥饿)可选公平锁或非公平锁
灵活性自动获取/释放锁,功能简单支持tryLock、lockInterruptibly等高级功能
条件变量单一等待队列(wait/notify)支持多个Condition对象
中断响应不支持锁中断支持(lockInterruptibly)
性能JDK 6后优化(锁升级机制),性能接近ReentrantLock高并发场景下,非公平锁性能略优
使用场景简单同步场景(如方法级别同步)复杂场景(需要公平锁、条件变量、超时机制)

1.3 选择建议

  • Synchronized:适合简单场景,代码简洁,JVM优化成熟,推荐用于低并发或简单同步。
  • ReentrantLock:适合复杂场景,如需要公平锁、条件变量或中断响应,推荐用于高并发或复杂逻辑。

二、Synchronized的底层实现

synchronized的实现依赖JVM和操作系统的互斥机制,核心在于对象头(Mark Word)和锁升级机制。

2.1 对象头与Mark Word

Java对象在内存中由三部分组成:对象头实例数据对齐填充。对象头包含:

  • Mark Word:存储锁状态、哈希码、GC标记等。
  • Class Metadata Address:指向类的元数据。

Mark Word的结构(64位JVM):

状态Mark Word结构(64位)
无锁`hash:31
偏向锁`thread:54
轻量级锁`ptr_to_lock_record:62
重量级锁`ptr_to_monitor:62
  • lock字段:2位标志锁状态(01: 无锁/偏向锁,00: 轻量级锁,10: 重量级锁)。
  • biased_lock:1位标志是否启用偏向锁。
  • thread:偏向锁记录持有锁的线程ID。
  • ptr_to_lock_record:轻量级锁指向栈中的锁记录。
  • ptr_to_monitor:重量级锁指向Monitor对象。

2.2 锁升级过程

为了提高性能,synchronized采用锁升级机制,从无锁逐步升级到重量级锁:

  1. 无锁:对象刚创建,无线程竞争。

  2. 偏向锁

    • 启用条件:只有一个线程访问同步块。
    • 实现:Mark Word记录线程ID,线程再次进入无需CAS。
    • 开销:极低,仅需检查线程ID。
    • 撤销:其他线程竞争时,暂停持有锁的线程,升级为轻量级锁。
  3. 轻量级锁

    • 启用条件:多个线程交替竞争,但无激烈争用。
    • 实现:线程在栈中创建Lock Record,尝试用CAS将Mark Word替换为Lock Record指针。
    • 开销:CAS操作,低于重量级锁。
    • 升级:竞争加剧(如自旋失败),升级为重量级锁。
  4. 重量级锁

    • 启用条件:激烈竞争,线程需要阻塞。
    • 实现:JVM创建Monitor对象,Mark Word指向Monitor,线程进入等待队列。
    • 开销:高,涉及系统调用(如Linux的futex)。

2.3 互斥锁的系统调用

重量级锁依赖操作系统内核的互斥锁(Mutex)。在Linux上,JVM通过futex(Fast Userspace Mutex)系统调用实现:

  • 加锁:调用futex检查锁状态,若锁被占用,线程进入内核态等待。
  • 释放锁:调用futex唤醒等待线程。
  • 开销:用户态到内核态切换,上下文切换成本高。

三、ReentrantLock的底层实现

ReentrantLock基于AQS(AbstractQueuedSynchronizer),通过CAS和队列实现高效锁机制。

3.1 AQS核心结构

AQS是一个同步框架,核心组件包括:

  • state:同步状态(int类型,ReentrantLock中表示锁的重入次数)。
  • CLH队列:双向链表,存储等待线程(Node对象)。
  • Node:队列节点,包含线程、等待状态(SHARED/EXCLUSIVE)等。

AQS提供两种模式:

  • 独占模式:如ReentrantLock,一次只有一个线程持有锁。
  • 共享模式:如Semaphore,允许多个线程共享资源。

3.2 ReentrantLock的实现

ReentrantLock通过内部类Sync(继承AQS)实现锁逻辑:

  • 非公平锁(NonfairSync)

    • 线程直接尝试CAS获取锁(compareAndSetState),成功则持有锁,失败则入队。
    • 优点:减少上下文切换,提高吞吐量。
    • 缺点:可能导致线程饥饿。
  • 公平锁(FairSync)

    • 检查队列是否有等待线程(hasQueuedPredecessors),若有则入队,否则尝试获取锁。
    • 优点:保证先来先得,减少饥饿。
    • 缺点:频繁检查队列,性能略低。

加锁流程

  1. 调用lock(),尝试通过CAS设置state(0->1)。
  2. 若成功,设置当前线程为锁持有者。
  3. 若失败,加入CLH队列,线程挂起(LockSupport.park)。
  4. 锁释放时,唤醒队列头部线程(LockSupport.unpark)。

重入性

  • 若同一线程再次加锁,state递增(state++)。
  • 释放锁时,state递减,直到state=0才完全释放。

3.3 模板方法模式

AQS采用模板方法模式,将锁的具体逻辑交给子类实现:

  • 核心方法

    • tryAcquire:尝试获取锁(子类实现)。
    • tryRelease:尝试释放锁(子类实现)。
    • acquireQueued:入队并等待(AQS实现)。
  • ReentrantLock实现

    • NonfairSync.tryAcquire:非公平尝试获取锁。
    • FairSync.tryAcquire:检查队列后尝试获取锁。

3.4 CAS机制

CAS(Compare-And-Swap)是ReentrantLock的核心原子操作,基于硬件指令(如cmpxchg):

  • 原理:比较内存值与预期值,若相等则更新为新值。

  • 实现:Java通过Unsafe类的compareAndSwapInt调用本地方法。

  • 问题

    • ABA问题:值从A->B->A,可能掩盖变化。AQS通过state单调递增避免。
    • 自旋开销:CAS失败时可能多次重试,消耗CPU。
  • 优化:AQS结合队列和LockSupport,减少无谓自旋。


四、知识体系与脉络

通过synchronizedReentrantLock的剖析,我们可以构建以下JUC知识体系:

  1. 锁机制

    • synchronized:JVM内置锁,依赖对象头和Monitor。
    • ReentrantLock:AQS实现的显式锁,灵活性强。
  2. 并发原语

    • CAS:原子操作,ReentrantLock和AQS的核心。
    • LockSupport:线程阻塞与唤醒,AQS队列管理的基础。
  3. 锁优化

    • synchronized:偏向锁、轻量级锁、重量级锁。
    • ReentrantLock:公平/非公平锁、条件变量。
  4. 底层支撑

    • 对象头(Mark Word):锁状态存储。
    • 系统调用(futex):重量级锁的内核支持。
    • AQS:JUC的核心框架,支撑ReentrantLockSemaphore等。

面试应对脉络

  • 表层:从功能比较入手,清晰说明两者的异同和使用场景。
  • 中层:深入锁升级(synchronized)和AQS(ReentrantLock),展示对机制的理解。
  • 底层:剖析Mark Word、系统调用、CAS等,体现对JVM和操作系统的掌握。
  • 扩展:结合JUC其他组件(如ConditionCountDownLatch),展示知识广度。

五、总结

synchronizedReentrantLock各有千秋,前者简单高效,适合简单场景;后者灵活强大,适合复杂并发逻辑。通过锁升级、Mark Word、AQS、CAS等底层机制的剖析,我们不仅理解了两者的实现原理,还构建了从表层到底层的JUC知识体系。在面试中,清晰的逻辑、深入的分析和全面的知识脉络将帮助你从容应对面试官的“拷打”。