JavaGuide项目解析:深入理解AQS(AbstractQueuedSynchronizer)原理

JavaGuide项目解析:深入理解AQS(AbstractQueuedSynchronizer)原理

JavaGuide JavaGuide:这是一份Java学习与面试指南,它涵盖了Java程序员所需要掌握的大部分核心知识。这份指南是一份通俗易懂、风趣幽默的学习资料,内容全面,深受Java学习者的欢迎。 JavaGuide 项目地址: https://gitcode.com/gh_mirrors/ja/JavaGuide

什么是AQS?

AQS(AbstractQueuedSynchronizer)是Java并发包中的一个核心框架,位于java.util.concurrent.locks包下。它为构建锁和同步器提供了通用功能的实现,是Java并发编程中最重要的基础组件之一。

AQS的核心作用

AQS主要解决了开发者在实现同步器时的复杂性问题。它提供了一个通用框架,用于实现各种同步器,例如:

  • 可重入锁(ReentrantLock)
  • 信号量(Semaphore)
  • 倒计时器(CountDownLatch)
  • 读写锁(ReentrantReadWriteLock)

通过封装底层的线程同步机制,AQS将复杂的线程管理逻辑隐藏起来,使开发者只需专注于具体的同步逻辑。

AQS的核心原理

状态管理

AQS使用一个volatile修饰的int类型变量state来表示同步状态:

private volatile int state;

状态信息可以通过以下方法进行操作:

protected final int getState() {
    return state;
}

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

protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

不同的同步器对state的解释不同:

  • 在ReentrantLock中,state表示锁的持有计数
  • 在Semaphore中,state表示可用许可数量
  • 在CountDownLatch中,state表示计数器值

CLH队列变体

AQS内部使用CLH锁队列的变体来管理等待线程。CLH锁是一种基于自旋锁的优化实现,AQS对其进行了以下改进:

  1. 自旋+阻塞混合机制:先短暂自旋尝试获取锁,失败后进入阻塞状态
  2. 单向队列改为双向队列:便于直接唤醒后继节点

节点状态

AQS中的等待队列节点(Node)有不同的状态:

| 状态 | 值 | 含义 | |-------------|------|----------------------------------------------------------------------| | CANCELLED | 1 | 线程已取消获取锁 | | SIGNAL | -1 | 后继节点需要当前节点唤醒 | | CONDITION | -2 | 节点在等待Condition | | PROPAGATE | -3 | 用于共享模式下解决线程无法唤醒的问题 | | 0 | 0 | 新节点的初始状态 |

AQS的使用方式

自定义同步器

基于AQS实现自定义同步器通常需要以下步骤:

  1. 继承AbstractQueuedSynchronizer
  2. 重写AQS提供的模板方法

AQS提供了5个关键的模板方法:

protected boolean tryAcquire(int arg)  // 独占方式尝试获取资源
protected boolean tryRelease(int arg)  // 独占方式尝试释放资源
protected int tryAcquireShared(int arg)  // 共享方式尝试获取资源
protected boolean tryReleaseShared(int arg)  // 共享方式尝试释放资源
protected boolean isHeldExclusively()  // 判断是否独占资源

资源共享模式

AQS支持两种资源共享方式:

  1. 独占模式(Exclusive):只有一个线程能执行,如ReentrantLock
  2. 共享模式(Share):多个线程可同时执行,如Semaphore/CountDownLatch

AQS源码深度解析

独占模式获取资源

核心方法是acquire()

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

流程分为三步:

  1. tryAcquire():尝试获取资源(由子类实现)
  2. addWaiter():获取失败时将线程封装为Node加入队列
  3. acquireQueued():在队列中等待获取资源
tryAcquire实现示例

以ReentrantLock的非公平锁为例:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
节点入队(addWaiter)
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
队列中等待(acquireQueued)
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

独占模式释放资源

核心方法是release()

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

以ReentrantLock为例:

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
唤醒后继节点(unparkSuccessor)
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

总结

AQS是Java并发编程的核心框架,它通过状态管理和CLH队列变体实现了高效的线程同步机制。理解AQS的工作原理对于掌握Java并发编程至关重要,也是深入理解各种同步器实现的基础。通过本文的分析,希望读者能够对AQS有一个全面而深入的认识。

JavaGuide JavaGuide:这是一份Java学习与面试指南,它涵盖了Java程序员所需要掌握的大部分核心知识。这份指南是一份通俗易懂、风趣幽默的学习资料,内容全面,深受Java学习者的欢迎。 JavaGuide 项目地址: https://gitcode.com/gh_mirrors/ja/JavaGuide

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙肠浪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值