JUC发展、锁、AQS、CAS、Park核心解析

JUC发展、锁、AQS、CAS、Park核心解析

1.发展脉络与核心驱动力

核心驱动力:减少用户态内核态的切换开销,提升并发性能。

  • 内核态锁(synchronized早期):线程竞争锁时,会直接挂起(Block),进入内核等待队列。上下文切换开销巨大(~微秒级)。
  • 用户态锁(JUC目标):尽可能在用户态(Java代码层面)解决锁竞争问题,避免进入内核。只有不得已时,才寻求内核帮助。

发展主线
重量级内核锁 (synchronized) -> CAS(乐观自旋) -> AQS(锁的框架与队列管理) -> 更丰富的锁工具 (ReentrantLock, Semaphore等) -> 针对特定场景的优化 (StampedLock, LongAdder等)

2. 各项关键技术及其解决的问题

CAS (Compare-And-Swap, 比较并交换)

  • 是什么:一条CPU原子指令(如 cmpxchg)。操作:如果变量值==期望值,则更新为新值,否则什么都不做。 整个过程不可中断。
  • 解决的问题
    1. 替代部分锁,实现无锁(乐观锁)编程。在竞争不激烈的场景下,性能远高于锁。
    2. 作为构建更高级并发工具的原子操作基石。AQS、原子类(AtomicInteger)等都依赖CAS。
  • 带来的新问题
    1. ABA问题:一个值从A变成B又变回A,CAS会误以为没变过。通过添加版本号(AtomicStampedReference)解决。
    2. 自旋开销:竞争激烈时,线程会长时间循环尝试CAS,消耗CPU。这引出了需要排队和挂起线程的需求 -> AQS

AQS (AbstractQueuedSynchronizer, 抽象队列同步器)

  • 是什么:JUC锁的核心框架。它用一个int表示状态,一个FIFO的CLH双向队列来管理等待线程。
  • 解决的问题
    1. 提供了锁的通用实现模板。让锁的实现者(如ReentrantLock)只需关注如何维护state状态(尝试获取、释放),而复杂的线程排队、阻塞、唤醒机制都由AQS搞定。这是“模板方法模式”的典范。
    2. 高效管理竞争线程。当CAS快速获取锁失败后,线程会被封装成节点,通过park进入等待队列,避免了无休止的自旋消耗CPU。
    3. 奠定了JUC中大部分同步工具的基础ReentrantLockSemaphoreCountDownLatchReentrantReadWriteLock 等都是基于AQS构建的。

Park/Unpark (LockSupport类)

  • 是什么LockSupport.park()LockSupport.unpark(Thread),是Java层面线程阻塞(挂起)唤醒的最底层操作。
  • 解决的问题
    1. 提供了比 Object.wait()/notify() 更灵活、更底层的线程控制
      • unpark 可以在 park 之前调用,使其下次park不阻塞。
      • 无需先获得对象的监视器锁。
    2. 是AQS实现线程挂起和唤醒的底层机制。AQS中,竞争锁失败的线程,最终就是通过调用 park 将自己挂起。当锁释放时,队列中的下一个线程会被 unpark 唤醒。
    3. 精准控制线程状态,是构建上层同步器的“积木”。

对应的锁 (JUC Lock)

以最经典的 ReentrantLock 为例:

  • 是什么:基于AQS实现的可重入、可中断、可选择公平/非公平的互斥锁。

  • 解决的问题

    1. 功能扩展:解决了 synchronized 功能单一的问题,提供了:
      • 可中断锁lockInterruptibly(),在等待锁的过程中可以响应中断。
      • 尝试获取锁tryLock(),获取不到立即返回或等待指定时间。
      • 公平性选择:可以减少线程“饥饿”现象。
    2. 性能优化(在早期Java版本中显著):在激烈竞争、高度并发场景下,性能优于早期的 synchronized
    • 注意:随着synchronized的不断优化(偏向锁、轻量级锁、自旋锁、锁消除、锁粗化等),两者性能差距已不明显。现在选择ReentrantLock通常是因为其高级功能,而非纯性能。

总结:它们如何协同工作

让我们串联一个 ReentrantLock.lock() 的典型流程,看看这些技术如何协作:

  1. 线程A调用 lock()
  2. 底层会先用一次 CAS 尝试快速设置锁状态(state从0变1)。如果成功,线程A获取锁。
  3. 如果失败(锁已被线程B持有),则进入 AQS 的队列管理逻辑。
  4. AQS将线程A封装成节点,加入CLH队列尾部。可能再次尝试获取(非公平锁特性)。
  5. 如果最终确定需要等待,AQS会调用 LockSupport.park() 将线程A挂起(阻塞),避免CPU空转。
  6. 当持有锁的线程B调用 unlock() 时,会通过AQS更新状态,并找到队列中合适的节点(如头节点的后继),调用 LockSupport.unpark() 唤醒对应的线程A。
  7. 线程A被唤醒后,会再次通过 CAS 去尝试获取锁状态,成功后出队并继续执行。

演进图景

synchronized (重量级锁,内核切换)
      ↓ (追求用户态解决)
CAS (原子操作,无锁基础,但自旋有开销)
      ↓ (需要管理竞争)
AQS (锁框架,管理排队和状态)
      + Park/Unpark (精准控制线程阻塞/唤醒)
      ↓
ReentrantLock, Semaphore, CountDownLatch... (丰富的并发工具)
      ↓ (解决更细分场景的问题)
StampedLock (乐观读,解决读写锁饥饿)
LongAdder (高并发统计,分散热点)

核心思想:从“一有竞争就找内核帮忙”的粗放模式,演进为“先在用户态自旋尝试(CAS) -> 不行就排队(AQS队列) -> 最后才挂起(Park)”的精细化、分层管理模式,从而极大提升了并发性能。

JUC并发原语技术演进对比

技术 出现时间 解决的问题 性能特点
synchronized JDK 1.0 提供最基本的线程互斥同步能力,内置语言特性 早期重量级(内核态切换)→ 经过锁升级优化(偏向锁/轻量级锁/重量级锁)后成为混合型锁
CAS JDK 1.5 实现无锁并发操作,解决原子更新问题,避免线程阻塞 竞争不激烈时性能极高(用户态操作),但激烈竞争时自旋消耗CPU,存在ABA问题
AQS JDK 1.5 提供同步器统一框架,将线程排队、阻塞/唤醒机制抽象化 模板方法设计,极大降低同步器实现复杂度,但复杂锁逻辑可能引入额外开销
LockSupport.park/unpark JDK 1.5 提供线程阻塞/唤醒的底层原语,比wait/notify更灵活可控 轻量级线程控制,不依赖监视器锁,是AQS实现线程挂起的核心基础
ReentrantLock JDK 1.5 基于AQS的显式锁,提供比synchronized更灵活的锁控制 支持可中断、超时、公平锁等特性,高竞争场景早期性能优势明显。ReentrantLock需要用到CAS、AQS、LockSupport.park/unpark
StampedLock JDK 1.8 解决读写锁的"写饥饿"问题,提供乐观读模式 在读多写少场景性能更高,但API复杂,不支持重入和条件变量

ReentrantLock的成功在于(使用CAS、AQS、LockSupport.park/unpark):

  • 性能平衡:结合了CAS的速度和Park的节能
  • 功能完备:基于AQS实现了可重入、公平/非公平、条件变量等
  • 扩展性强:AQS框架支持多种同步器

这种"CAS快速尝试 → AQS队列管理 → Park节能挂起"的三层架构,成为JUC中高性能同步器的标准设计模式。

┌─────────────────────────────────────────────────────┐
│                  JUC 并发工具层                        │
│  ReentrantLock  CountDownLatch  Semaphore  ...       │
└─────────────────────────────────────────────────────┘
                            ↓ 基于
┌─────────────────────────────────────────────────────┐
│                AQS (同步框架层)                        │
│    同步状态(state) + CLH队列 + park/unpark机制        │
└─────────────────────────────────────────────────────┘
                 ↓ 使用               ↓ 使用
┌────────────────┐         ┌────────────────────┐
│   CAS 层        │         │  park/unpark 层    │
│  Unsafe.compare │         │ LockSupport.park() │
│  AndSwap()      │         │ LockSupport.unpark│
└────────────────┘         └────────────────────┘
        ↓                          ↓
┌─────────────────────────────────────────────────────┐
│            JVM/HotSpot 虚拟机层                       │
│     本地方法实现(cpp) + 操作系统原语支持                │
└─────────────────────────────────────────────────────┘
posted @ 2026-01-26 19:36  deyang  阅读(2)  评论(0)    收藏  举报