高级java每日一道面试题-2024年7月29日-并发篇-你对AQS框架了解多少?

面试官: 你对AQS框架了解多少?

我回答:

AQS(AbstractQueuedSynchronizer)是 Java 并发包 java.util.concurrent 中的一个抽象类,它是许多同步工具类的基础,如 ReentrantLock, Semaphore, CountDownLatch 等。AQS 提供了一套完整的实现锁和同步器的框架,使得开发者可以基于它构建自己的同步组件。
下面我将详细介绍 AQS 的核心概念、工作原理以及如何使用 AQS 构建自定义同步器。

AQS 的核心概念

AQS(AbstractQueuedSynchronizer)是JDK提供的一个用于实现基于FIFO(First In, First Out)等待队列的阻塞锁和同步器的基础框架。它定义了一套多线程访问共享资源的同步器框架,许多Java中的同步器都是基于AQS实现的,如ReentrantLock、Semaphore、CountDownLatch等。

  1. 同步状态 (state):

    • AQS 维护了一个volatile修饰的int类型变量,表示同步状态。对于独占锁,state表示锁的持有状态(0表示未持有,1表示持有);对于共享锁,state表示当前持有的读锁数量。通过CAS(Compare-And-Swap)操作来保证状态变量的原子性更新。
    • 同步状态可以用来表示锁的持有情况、信号量的许可数量等。
    • state 的值可以通过 getState(), setState(int newState), compareAndSetState(int expect, int update) 方法来读取、设置或原子地更新。
  2. 条件队列 (CLH queue):

    • CLH (Craig-Landin-Baker) 队列是一种特殊的 FIFO 队列,用于存储等待获取同步状态的线程。
    • 当线程尝试获取同步状态失败时,会被插入到条件队列中等待。
    • 条件队列中的节点类型为 Node,每个节点代表一个等待的线程。
  3. 独占式与共享式同步:

    • 独占式同步:一次只有一个线程可以获得同步状态,如 ReentrantLock。
    • 共享式同步:允许多个线程同时获得同步状态,如 Semaphore。

AQS 的工作原理

  1. 获取同步状态:

    • 当线程调用 acquiretryAcquire 方法时,AQS 会尝试获取同步状态。当线程尝试获取锁时,首先会检查同步状态是否满足(对于独占锁,检查state是否为0;对于共享锁,检查state是否允许更多的线程访问)。
    • 如果获取成功,则通过CAS操作尝试更新同步状态,线程可以继续执行。
    • 如果获取失败,线程会被插入到条件队列中,并挂起等待。
  2. 释放同步状态:

    • 当线程完成任务并释放同步状态时,AQS 会唤醒条件队列中的下一个节点对应的线程,使其有机会再次尝试获取同步状态。
  3. 线程加入等待队列和等待队列中的线程:

    • 未获得锁的线程会被封装成一个Node节点,并加入到等待队列的末尾。
    • 节点之间通过prev和next指针相互连接,形成一个双向链表。
    • 等待的线程会被封装成 Node 类型的对象,并被插入到条件队列中。
    • 当前线程会通过 LockSupport.park(this) 方法挂起自身,等待被唤醒。
  4. 线程阻塞与唤醒:

    • 未获得锁的线程会通过Unsafe类中的park方法被阻塞。
    • 当持有锁的线程释放锁时,当同步状态被释放时,AQS 会从条件队列中选择一个节点,并将其对应的线程唤醒。
    • 被唤醒的线程会再次尝试获取同步状态。

AQS 的关键方法

  1. tryAcquire:

    • 这是一个模板方法,由子类实现,用于尝试获取同步状态。
    • 如果成功,返回 true;如果失败,返回 false
  2. tryRelease:

    • 这也是一个模板方法,由子类实现,用于尝试释放同步状态。
    • 如果成功,返回 true;如果失败,返回 false
  3. acquire:

    • tryAcquire 失败时,调用此方法将线程插入条件队列,并可能挂起线程。
    • 此方法会循环尝试获取同步状态,直到成功。
  4. release:

    • 当线程释放同步状态时调用此方法。
    • 此方法会唤醒条件队列中的下一个节点对应的线程。
  5. tryAcquireShared:

    • 在共享模式下尝试获取同步状态。
  6. tryReleaseShared:

    • 在共享模式下尝试释放同步状态。
  7. isHeldExclusively:

    • 检查当前线程是否正在独占资源。

应用场景

AQS作为Java并发编程的重要基石,广泛应用于各种同步器的实现中。以下是一些基于AQS实现的同步器及其应用场景:

  • ReentrantLock:一种可重入的独占锁,支持公平锁和非公平锁两种模式。
  • Semaphore:一种计数信号量,用于控制对资源的访问数量,可以在独占模式和共享模式下使用。
  • CountDownLatch:一种同步工具,允许一个或多个线程等待一组操作完成。
  • ReadWriteLock:读写锁,允许多个线程同时读取共享资源,但写入操作是互斥的。

自定义同步器示例

下面是一个简单的自定义同步器示例,实现了一个简单的计数锁,它可以被多个线程共享:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class CountingLock extends AbstractQueuedSynchronizer {

    private static final long serialVersionUID = 1L;

    @Override
    protected boolean tryAcquire(int arg) {
        // 尝试获取同步状态
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    @Override
    protected boolean tryRelease(int arg) {
        // 释放同步状态
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

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

    public boolean tryLock() {
        return tryAcquire(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return acquireQueuedInterruptibly(new Node(), 1);
    }

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

    public boolean isLocked() {
        return getState() == 1;
    }

    public static void main(String[] args) {
        CountingLock countingLock = new CountingLock();

        // 测试锁的功能
        Thread t1 = new Thread(() -> {
            countingLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " acquired the lock");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countingLock.unlock();
            }
        });

        t1.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread t2 = new Thread(() -> {
            countingLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " acquired the lock");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countingLock.unlock();
            }
        });

        t2.start();
    }
}

在这个示例中,我们定义了一个名为 CountingLock 的类,它继承自 AbstractQueuedSynchronizer。我们实现了 tryAcquiretryRelease 方法来控制同步状态的获取和释放。此外,我们还定义了一些公共方法来方便外部调用。

总结

AQS是Java并发编程中的一个重要组件,它提供了一个构建锁和同步器的框架,通过状态变量和等待队列的管理,实现了线程间的同步和协调。理解AQS的工作原理和应用场景,对于编写高效、可靠的并发程序具有重要意义。在面试中,能够清晰地阐述AQS的核心思想、工作流程和应用场景,将有助于提高面试的通过率。

  • 14
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

java我跟你拼了

您的鼓励是我创作的最大动力!

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

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

打赏作者

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

抵扣说明:

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

余额充值