AQS(AbstractQueuedSynchronizer)详解

标题:AQS(AbstractQueuedSynchronizer)详解
摘要:
个人总结一些java的基础面试问题,个人理解,如有理解偏差,可进行讨论,勿喷!!!


AbstractQueuedSynchronizer(简称 AQS)是 Java 并发包(JUC)的核心基础组件,用于构建锁(如ReentrantLock)、信号量(Semaphore)、倒计时门闩(CountDownLatch)等同步工具。它通过一个FIFO 队列和一个 int 类型的状态变量(state) 来实现同步机制。

核心原理与结构
一、AQS 核心结构
AQS 是 Java 并发包(java.util.concurrent)的核心基础框架,通过一个双向链表(CLH 队列)和volatile 变量 state来实现锁和同步器的机制。其核心组成部分包括:

状态变量 state
类型为volatile int,用于表示同步状态(如锁的持有次数、信号量剩余数量等)。
通过getState()、setState(int)和compareAndSetState(int expect, int update)(CAS 操作)修改状态,保证可见性和原子性。

双向链表(CLH 队列)
存储等待获取同步状态的线程,节点类型为Node,每个节点包含:
thread:当前等待线程。
waitStatus:节点状态(如CANCELLED、SIGNAL、CONDITION等)。
prev/next:前驱 / 后继节点指针。
链表头节点(head)表示当前持有锁的线程,尾节点(tail)用于新节点插入。

Node 节点状态
CANCELLED(1):节点因超时或中断被取消,不再参与同步。
SIGNAL(-1):后继节点需要被唤醒(当前节点释放锁时,会通知后继节点)。
CONDITION(-2):节点在条件队列中等待(与Condition配合使用)。
PROPAGATE(-3):共享模式下,状态变更需要传播给后续节点。
0:初始状态。

二、AQS 工作原理
AQS 通过模板方法模式提供了一套通用的同步机制,分为独占模式(如 ReentrantLock)和共享模式(如 Semaphore),核心流程如下:

(一)独占模式(以获取锁为例)
获取锁(acquire (int arg))
先通过tryAcquire(int arg)尝试直接获取锁(由子类实现,如 ReentrantLock 的公平 / 非公平逻辑)。
若失败,将当前线程包装为独占模式节点(Node.EXCLUSIVE),加入 CLH 队列尾部。
线程进入循环,检查前驱节点是否为头节点且能否获取锁:
若是,获取锁并将自己设为头节点(旧头节点出队,避免持有锁的线程被阻塞)。
否则,通过LockSupport.park()阻塞当前线程,并在被唤醒后重复检查。
释放锁(release (int arg))
调用tryRelease(int arg)尝试释放锁(子类实现,如减少 state 计数)。
若释放成功,检查后继节点是否需要唤醒(状态为SIGNAL):
若是,通过LockSupport.unpark(node.thread)唤醒后继线程。

(二)共享模式(以获取信号量为例)
获取共享资源(acquireShared (int arg))
先通过tryAcquireShared(int arg)尝试获取资源(如信号量剩余数量是否足够)。
若失败,加入 CLH 队列,线程阻塞。
成功获取资源后,若还有剩余资源,唤醒后续共享节点(支持批量唤醒)。
释放共享资源(releaseShared (int arg))
通过tryReleaseShared(int arg)释放资源(如增加信号量计数)。
若释放成功,唤醒所有可获取资源的后继节点。

(三)关键设计点
CLH 队列的优化:通过 CAS 保证尾节点插入的原子性,避免多线程竞争。
线程阻塞与唤醒:使用LockSupport(基于 Unsafe)实现线程挂起和恢复,比synchronized的wait/notify更高效。
可见性保证:state和Node的waitStatus均为volatile,确保多线程间状态可见。

三、项目实战应用场景
AQS 是 Java 并发工具的底层实现,以下是常见应用场景及示例:
(一)独占锁:ReentrantLock
场景:需要互斥访问的临界资源(如缓存更新、文件写入)。
示例代码:


private ReentrantLock lock = new ReentrantLock();

public void updateCache() {
    lock.lock(); // 独占模式获取锁
    try {
        // 临界区操作
    } finally {
        lock.unlock(); // 释放锁
    }
}

原理:
ReentrantLock 的公平锁 / 非公平锁通过 AQS 的tryAcquire实现。
锁重入通过state计数实现(如可重入次数)。

(二)共享锁:Semaphore
场景:控制多线程对有限资源的访问(如数据库连接池、线程池任务数)。
示例代码:

private Semaphore semaphore = new Semaphore(3); // 最多3个线程同时访问

public void handleRequest() throws InterruptedException {
    semaphore.acquire(); // 共享模式获取许可
    try {
        // 处理请求
    } finally {
        semaphore.release(); // 释放许可
    }
}

原理:
state表示剩余许可数,tryAcquireShared检查state是否大于 0。
释放时通过tryReleaseShared增加state,并唤醒后续共享节点。

(三)读写锁:ReentrantReadWriteLock
场景:读多写少的场景(如缓存读多写少),允许多个读线程共享访问,写线程独占
示例代码:

private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
private ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

public void readData() {
    readLock.lock(); // 共享模式获取读锁
    try {
        // 读操作
    } finally {
        readLock.unlock();
    }
}

public void writeData() {
    writeLock.lock(); // 独占模式获取写锁
    try {
        // 写操作
    } finally {
        writeLock.unlock();
    }
}

原理:
读锁(共享模式)允许多个线程同时持有,写锁(独占模式)互斥。
通过state的高 16 位(读锁计数)和低 16 位(写锁重入计数)实现状态分离。

(四)自定义同步器:实现简单的屏障(Barrier)
场景:多个线程等待彼此到达某个状态后再继续执行(类似 CyclicBarrier)
自定义实现思路:
继承 AQS,使用共享模式,state表示剩余等待线程数。
tryAcquireShared:若state > 0,线程加入队列等待;若state == 0,直接通过。
tryReleaseShared:释放时减少state,若state为 0,唤醒所有等待线程。
示例代码:

public class SimpleBarrier {
    private static class BarrierSync extends AbstractQueuedSynchronizer {
        BarrierSync(int parties) {
            setState(parties); // 初始等待线程数
        }

        @Override
        protected int tryAcquireShared(int arg) {
            return getState() == 0 ? 1 : -1; // state为0时允许通过(返回1表示成功)
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            // 每次释放减少state,若减至0则唤醒所有等待线程
            for (;;) {
                int c = getState();
                if (c == 0) return false;
                if (compareAndSetState(c, c - 1)) {
                    return c - 1 == 0; // 若减至0,返回true触发唤醒
                }
            }
        }
    }

    private final BarrierSync sync;

    public SimpleBarrier(int parties) {
        sync = new BarrierSync(parties);
    }

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1); // 共享模式获取,支持中断
    }

    public void signal() {
        sync.releaseShared(1); // 释放信号,减少等待数
    }
}

四、AQS 的优缺点与适用场景
优点:
基于 CAS 和 volatile,性能优于synchronized(尤其在竞争激烈时)。
支持灵活的同步模式(独占 / 共享),便于扩展自定义同步器。
缺点:
学习成本高,需深入理解 CLH 队列和状态管理。
子类需正确实现tryAcquire/tryRelease等方法,否则易导致死锁或性能问题。
适用场景:
高并发场景下的互斥锁、共享锁需求。
需要自定义同步逻辑(如屏障、计数锁等)。

五、总结
AQS 通过双向链表 + 状态变量的设计,实现了高效的线程阻塞与唤醒机制,是 Java 并发包的基石。理解 AQS 的核心结构和工作原理,有助于深入掌握 ReentrantLock、Semaphore 等工具的底层逻辑,并在实际项目中合理运用或自定义同步器,优化高并发场景下的性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值