Java面试--AQS

本文详细介绍了AQS(AbstractQueuedSynchronizer)的核心概念,包括state的使用、资源的独占与共享方式、底层的CLH队列以及在ReentrantLock和Semaphore中的应用。AQS是Java并发包的基础,用于实现锁和并发工具类。通过AQS,开发者可以方便地构建自定义同步器,同时支持独占和共享模式。文章还探讨了ReentrantLock的公平锁与非公平锁的实现原理,并给出了Semaphore的简单应用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、AQS概述

AQS 全名 AbstractQueuedSynchronizer,意为抽象队列同步器,JUC(java.util.concurrent 包)下面的 Lock 和其他一些并发工具类都是基于它来实现的。AQS 维护了一个 volatile 的 state 和一个 CLH(FIFO)双向队列

在这里插入图片描述

二、分析

2.1、state

AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改

private volatile int state;   // 共享变量,使用 volatile 修饰保证线程可见性

状态信息通过 protected 类型的 方法进行操作

  • getState()
  • setState()
  • compareAndSetState()

getState:返回同步状态的当前值

protected final int getState() {
   
   
	return state;
}

setState:设置同步状态的值

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

compareAndSetState:原子地(CAS操作)将同步状态值设置为给定值 update 如果当前同步状态的值等于 expect(期望值)

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

2.2、AQS对资源的共享方式

1、Exclusive(独占)

只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁,ReentrantLock 同时支持两种锁,下面以 ReentrantLock 对这两种锁的定义做介绍:

  • 公平锁 :按照线程在队列中的排队顺序,先到者先拿到锁
  • 非公平锁 :当线程要获取锁时,先通过两次 CAS 操作去抢锁,如果没抢到,当前线程再加入到队列中等待唤醒。

2、Share(共享)

多个线程可同时执行,如 Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。

ReentrantReadWriteLock 可以看成是组合式,因为 ReentrantReadWriteLock 也就是读写锁允许多个线程同时对某一资源进行读。

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS 已经在上层已经帮我们实现好了

2.3、AQS 底层使用了模板方法模式

同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):

  1. 使用者继承 AbstractQueuedSynchronizer 并重写指定的方法。(这些重写方法很简单,无非是对于共享资源 state 的获取和释放)
  2. 将 AQS 组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。

这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用

AQS 使用了模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的钩子方法:

// 独占方式。尝试获取资源,成功则返回 true,失败则返回 false
protected boolean tryAcquire(int)

// 独占方式。尝试释放资源,成功则返回 true,失败则返回 false
protected boolean tryRelease(int)

// 共享方式。尝试获取资源。负数表示失败;0 表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源
protected int tryAcquireShared(int)

// 共享方式。尝试释放资源,成功则返回 true,失败则返回 false
protected boolean tryReleaseShared(int)

// 该线程是否正在独占资源。只有用到 condition 才需要去实现它
protected boolean isHeldExclusively()

什么是钩子方法呢? 钩子方法是一种被声明在抽象类中的方法,一般使用 protected 关键字修饰,它可以是空方法(由子类实现),也可以是默认实现的方法。模板设计模式通过钩子方法控制固定步骤的实现

除了上面提到的钩子方法之外,AQS 类中的其他方法都是 final ,所以无法被其他类重写

以 ReentrantLock 为例

state 初始化为 0,表示未锁定状态。A 线程 lock() 时,会调用 tryAcquire() 独占该锁并将 state + 1 。此后,其他线程再 tryAcquire() 时就会失败,直到 A 线程 unlock() 到 state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多少次,这样才能保证 state 是能回到零态的

再以 CountDownLatch 以例

任务分为 N 个子线程去执行,state 也初始化为 N(注意 N 要与线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后 countDown() 一次,state 会 CAS(Compare and Swap) 减 1。等到所有子线程都执行完后(即 state=0 ),会 unpark() 主调用线程,然后主调用线程就会从 await() 函数返回,继续后余动作

一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现 tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但 AQS 也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock

2.4、CLH(FIFO)队列

AQS 中是通过内部类 Node 来维护一个 CLH 队列的。源码如下:

static final class Node {
   
   
    // 标记共享式访问
    static final Node SHARED = new Node();
    
    // 标记独占式访问
    static final Node EXCLUSIVE = null;

    // 字段 waitStatus 的值,表示当前节点已取消等待
    static final int CANCELLED =  1;
    
    // 字段 waitStatus 的值,表示当前节点取消或释放资源后,通知下一个节点
    static final int SIGNAL    = -1;
    
    // 表示正在等待触发条件
    static final int CONDITION = -2;
    
    // 表示下一个共享获取应无条件传播
    static final int PROPAGATE = -3;

     //Node对象存储标识的地
    volatile int waitStatus;

    // 前节点
    volatile Node prev;

    // 下一个节点
    volatile Node next;

    // 节点对应线程
    volatile Thread thread;

    // 下一个等待的节点
    Node nextWaiter;

    // 是否是共享式访问
    final boolean isShared() {
   
   
        return nextWaiter == SHARED;
    }

    // 返回前节点
    final Node predecessor() throws NullPointerException {
   
   
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {
   
       // 共享式访问的构造函数
    }

    Node(Thread thread, Node mode) {
   
        // 用于被添加等待者使用
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) {
   
    // 用于Condition使用
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

三、ReentrantLock

下面来看 ReentrantLock 中相关的源代码:

在这里插入图片描述

ReentrantLock 默认采用非公平锁,因为考虑获得更好的性能,通过 boolean 来决定是否用公平锁(传入 true 用公平锁)

3.1、非公平锁

1、非公平锁演示

@Test
public void testUnfairLock() throws InterruptedException {
   
   
    // 无参构造函数,默认创建非公平锁模式
    ReentrantLock reentrantLock = new ReentrantLock();


    for (int i = 0; i < 10; i++) {
   
   
        final int threadNum = i;
        new Thread(() -> {
   
   
            reentrantLock.lock();
            try {
   
   
                System.out.println("线程" + threadNum + "获取锁");
                Thread.sleep(1000);
            } catch (InterruptedExce
### Java AQS 面试常见问题及解答 #### 1. AQS 是什么? AQS (AbstractQueuedSynchronizer) 是一个用于构建锁和其他同步器组件的基础框架。它通过内部的一个 FIFO 线程等待队列来完成获取和释放资源的操作[^2]。 ```java public class MyLock extends AbstractQueuedSynchronizer { protected boolean tryAcquire(int arg) { /* ... */ } protected boolean tryRelease(int arg) { /* ... */ } } ``` #### 2. Node 节点的作用是什么? `Node` 节点用来管理等待队列中的线程状态以及排队顺序,确保线程可以按先进先出(FIFO) 的原则被唤醒并继续执行[^4]。 #### 3. AQS 中的独占模式和共享模式有何不同? - **独占模式**:一次只有一个线程可以获得资源,其他尝试获取该资源的线程会被加入到等待队列中直到前一线程释放资源为止。 - **共享模式**:允许多个线程同时持有资源,只要当前可用资源数量大于请求的数量即可成功获得许可。 #### 4. 如何自定义基于 AQS 实现自己的同步工具? 为了创建一个新的同步原语,通常需要继承 `AbstractQueuedSynchronizer` 类,并重写其中的一些受保护的方法如 `tryAcquire`, `tryRelease` 等以实现特定逻辑。 ```java class Mutex extends AbstractQueuedSynchronizer { // Attempts to acquire the lock exclusively. @Override protected boolean tryAcquire(int acquires) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } // Releases an exclusive hold on this synchronizer. @Override protected boolean tryRelease(int releases) { if (getState() == 0) throw new IllegalMonitorStateException(); setExclusiveOwnerThread(null); setState(0); return true; } public void lock() { acquire(1); } public void unlock() { release(1); } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

北极星小王子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值