面试题:说说 AQS吧?

AQS(AbstractQueuedSynchronizer) 是 Java 并发编程中的一个核心框架,位于 java.util.concurrent.locks 包中。它是构建锁和其他同步工具(如 ReentrantLockCountDownLatchSemaphore 等)的基础。AQS 通过一个 FIFO 队列(双向链表)来管理线程的排队和唤醒,并提供了一种灵活的机制来实现自定义的同步器。


1. AQS 的核心思想

  • 资源共享模式
    • AQS 支持两种资源共享模式:
    1. 独占模式(Exclusive):同一时刻只有一个线程可以访问资源,如 ReentrantLock
    2. 共享模式(Shared):同一时刻可以有多个线程访问资源,如 SemaphoreCountDownLatch
  • 状态管理
    • AQS 通过一个 volatile int state 变量来表示资源的状态。
    • 子类可以通过 getState()setState()compareAndSetState() 方法来操作状态。
  • 线程排队
    • AQS 使用一个双向链表(CLH 队列)来管理等待获取资源的线程。
    • 每个线程被封装为一个 Node 对象,包含线程引用、等待状态等信息。

2. AQS 的核心方法

  • 独占模式
    • tryAcquire(int arg):尝试获取资源,需要子类实现。
    • acquire(int arg):调用 tryAcquire,如果失败则将线程加入队列并阻塞。
    • tryRelease(int arg):尝试释放资源,需要子类实现。
    • release(int arg):调用 tryRelease,并唤醒队列中的下一个线程。
  • 共享模式
    • tryAcquireShared(int arg):尝试获取共享资源,需要子类实现。
    • acquireShared(int arg):调用 tryAcquireShared,如果失败则将线程加入队列并阻塞。
    • tryReleaseShared(int arg):尝试释放共享资源,需要子类实现。
    • releaseShared(int arg):调用 tryReleaseShared,并唤醒队列中的线程。

3. AQS 的实现原理

  • 状态变量(state)
    • state 是 AQS 的核心变量,表示资源的状态。
    • 例如,在 ReentrantLock 中,state 表示锁的重入次数;在 Semaphore 中,state 表示剩余的许可数。
  • CLH 队列
    • AQS 使用一个双向链表(CLH 队列)来管理等待线程。
    • 每个 Node 包含线程引用、等待状态(如 CANCELLEDSIGNAL)等信息。
  • 线程阻塞与唤醒
    • 当线程获取资源失败时,会被封装为 Node 并加入队列,然后通过 LockSupport.park() 阻塞。
    • 当资源释放时,AQS 会唤醒队列中的下一个线程(通过 LockSupport.unpark())。

4. AQS 的应用

  • ReentrantLock
    • ReentrantLock 是基于 AQS 实现的独占锁,支持可重入和公平/非公平模式。
  • Semaphore
    • Semaphore 是基于 AQS 实现的共享锁,用于控制同时访问资源的线程数。
  • CountDownLatch
    • CountDownLatch 是基于 AQS 实现的同步工具,用于等待一组线程完成任务。
  • ReentrantReadWriteLock
    • ReentrantReadWriteLock 是基于 AQS 实现的读写锁,支持读多写少的场景。

5. AQS 的代码示例

示例 1:自定义独占锁

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class MyLock {
    private final Sync sync = new Sync();

    // 自定义同步器
    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) { // CAS 操作
                setExclusiveOwnerThread(Thread.currentThread()); // 设置独占线程
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null); // 清除独占线程
            setState(0); // 释放锁
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }

    public void lock() {
        sync.acquire(1);
    }

    public void unlock() {
        sync.release(1);
    }
}

示例 2:使用自定义锁

public class MyLockExample {
    private static final MyLock lock = new MyLock();
    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                lock.lock();
                try {
                    count++;
                } finally {
                    lock.unlock();
                }
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("Final count: " + count); // 输出 2000
    }
}

6. AQS 的优缺点

  • 优点
    • 灵活性:AQS 提供了基础的同步框架,可以轻松实现各种同步工具。
    • 高性能:通过 CAS 和 CLH 队列,AQS 在高并发场景下性能优异。
  • 缺点
    • 复杂性:AQS 的实现较为复杂,需要深入理解其原理。
    • 扩展性:自定义同步器需要实现 AQS 的模板方法,对开发者要求较高。

7. 总结

  • AQS 是 Java 并发编程的核心框架,用于构建锁和其他同步工具。
  • 它通过状态变量和 CLH 队列管理线程的排队和唤醒,支持独占模式和共享模式。
  • ReentrantLockSemaphoreCountDownLatch 等工具都是基于 AQS 实现的。
  • 理解 AQS 的原理对于掌握 Java 并发编程至关重要。
THE END
点赞15 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容