前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >学习AQS:Java并发编程的基石

学习AQS:Java并发编程的基石

作者头像
崔认知
发布于 2025-05-08 07:33:51
发布于 2025-05-08 07:33:51
12000
代码可运行
举报
文章被收录于专栏:nobodynobody
运行总次数:0
代码可运行

Java并发编程中,AbstractQueuedSynchronizer(简称AQS)是构建同步器的核心框架。它通过提供状态管理线程排队阻塞唤醒机制,为开发者构建高效的线程协作工具提供了底层支持。


一、AQS的核心组成

AQS的内部结构由三个核心部分构成:状态(state)FIFO队列以及获取/释放方法。这三部分共同协作,实现了线程的高效调度与资源管理。

1. 状态(state)

AQS通过一个volatile int state变量来表示同步器的状态。state的含义由具体的子类决定,例如:

  • ReentrantLockstate表示锁的重入次数(0表示未被占用)。
  • Semaphorestate表示剩余许可证的数量。
  • CountDownLatchstate表示需要倒数的次数(减至0时触发事件)。
状态的线程安全操作

由于state会被多个线程并发修改,AQS通过CAS(Compare and Swap)操作和volatile关键字确保其线程安全性:

  • CAS操作:通过compareAndSetState(int expect, int update)方法,使用Unsafe类的原子指令更新state,保证操作的原子性。
  • 直接赋值:对于不需要依赖旧值的赋值操作(如setState(int newState)),volatile关键字确保了多线程间的可见性。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

protected final void setState(int newState) {
    state = newState;
}

2. FIFO队列

AQS维护了一个双向链表结构的等待队列,用于管理未获得资源的线程。该队列遵循FIFO(先进先出)原则,确保线程公平竞争资源。

队列结构与线程管理
  • 头节点(head):代表当前持有资源的线程。
  • 尾节点(tail):用于将新请求资源的线程添加到队列末尾。
  • 节点(Node):每个节点包含线程引用、状态(如等待、取消)以及前后指针。

当线程无法获取资源时,会被封装为Node对象插入队列尾部,并进入阻塞状态。释放资源时,AQS会唤醒队列中的下一个线程(即头节点的下一个节点),使其尝试获取资源。

线程阻塞与唤醒

AQS通过LockSupport.park()LockSupport.unpark()实现线程的阻塞与唤醒:

  • 阻塞:线程在等待资源时调用park(),进入阻塞状态。
  • 唤醒:资源释放后,调用unpark()唤醒队列中的下一个线程。

这种机制避免了线程的“忙等”,有效减少CPU资源消耗


3. 获取/释放方法

AQS定义了抽象的获取(acquire)和释放(release)方法,由具体的子类(如ReentrantLock、Semaphore)实现业务逻辑。这些方法的核心逻辑依赖于state的值和队列的管理。

获取方法(acquire)

获取方法的流程如下:

  1. 检查state:根据state的值判断是否允许当前线程获取资源。
  2. 资源不足时入队:若资源不可用,线程被封装为Node插入队列,并调用park()阻塞。
  3. 循环尝试:线程被唤醒后,再次尝试获取资源,直到成功或中断。

以ReentrantLock的lock()方法为例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void lock() {
    sync.acquire(1);
}

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
释放方法(release)

释放方法的流程如下:

  1. 更新state:根据业务逻辑调整state(如释放锁、归还许可证)。
  2. 唤醒队列中的线程:调用unpark()唤醒下一个等待线程。

以ReentrantLock的unlock()方法为例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

二、AQS的设计思想与优势

1. 模板方法模式

AQS通过模板方法模式(Template Method Pattern)将通用的线程调度逻辑抽象出来,子类只需实现特定的tryAcquiretryRelease方法。这种设计降低了代码重复性,提升了开发效率。

2. 模块化与可扩展性

AQS将状态管理、队列操作与业务逻辑解耦,开发者只需关注资源的获取与释放规则,而无需处理复杂的线程调度细节。例如,ReentrantLockSemaphore虽然业务逻辑不同,但都复用了AQS的队列和状态管理机制。

3. 性能与公平性

AQS支持非公平锁(默认)和公平锁(通过ReentrantLock(true)指定)。公平锁通过队列顺序保证线程按申请顺序获取资源,而非公平锁允许插队(如当前线程释放锁后立即重入),从而减少上下文切换开销。


三、AQS的实际应用案例

1. ReentrantLock的实现

ReentrantLock基于AQS实现了可重入锁:

  • 获取锁:通过tryAcquire检查state是否为0或当前线程是否已持有锁。
  • 释放锁:通过tryReleasestate减1,若减至0则唤醒等待线程。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            returntrue;
        }
    } elseif (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            thrownew Error("Maximum lock count exceeded");
        setState(nextc);
        returntrue;
    }
    returnfalse;
}

2. Semaphore的实现

Semaphore通过AQS管理许可证数量:

  • 获取许可证acquire()方法尝试减少state,若不足则阻塞。
  • 释放许可证release()方法增加state并唤醒等待线程。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

四、常见问题与扩展思考

1. 为什么AQS使用CAS而非锁?

CAS(Compare and Swap)是一种无锁算法,通过硬件指令(如cmpxchg)实现原子操作,避免了线程阻塞和上下文切换的开销。相比传统锁,CAS更适合高并发场景,但需处理ABA问题(通过版本号解决)。

2. AQS如何处理线程中断?

AQS通过interrupt()方法标记线程中断状态,并在acquire方法中检查中断标志。若线程在等待期间被中断,会抛出InterruptedException或在释放资源后自行处理。

3. AQS的扩展性如何?

AQS支持自定义同步器,开发者可通过继承AbstractQueuedSynchronizer并实现tryAcquiretryRelease方法,构建符合业务需求的同步工具(如数据库连接池、任务调度器)。


五、总结

AQS是Java并发编程的基石,其核心思想是通过状态管理FIFO队列模板方法模式 ,将线程调度的复杂性抽象为通用框架。掌握AQS的原理不仅有助于理解ReentrantLock、Semaphore等工具的实现,还能提升开发者设计高效并发组件的能力。无论是应对技术面试还是优化业务代码,深入学习AQS都是迈向高级Java开发者的必经之路。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 认知科技技术团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、AQS的核心组成
    • 1. 状态(state)
      • 状态的线程安全操作
    • 2. FIFO队列
      • 队列结构与线程管理
      • 线程阻塞与唤醒
    • 3. 获取/释放方法
      • 获取方法(acquire)
      • 释放方法(release)
  • 二、AQS的设计思想与优势
    • 1. 模板方法模式
    • 2. 模块化与可扩展性
    • 3. 性能与公平性
  • 三、AQS的实际应用案例
    • 1. ReentrantLock的实现
    • 2. Semaphore的实现
  • 四、常见问题与扩展思考
    • 1. 为什么AQS使用CAS而非锁?
    • 2. AQS如何处理线程中断?
    • 3. AQS的扩展性如何?
  • 五、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档