Skip to content
xiaoyang 的头像xiaoyang发布于 2025-03-1114,910 字阅读约 50 分钟浏览加载中

1. AQS概述 ​

1.1 AQS是什么? ​

AQS(AbstractQueuedSynchronizer)是 JUC(java.util.concurrent) 中用于构建同步器(锁、信号量等)的 基础框架,它是 Java并发包的核心组件之一。AQS 基于FIFO等待队列 实现线程的排队,并提供了一套基于 状态(state) 变量的 模板方法 来简化同步器的开发。

AQS 主要用于 构建锁和同步工具,如:

  • 独占模式(Exclusive):同一时间只能被一个线程获取,如 ReentrantLock。
  • 共享模式(Shared):多个线程可以同时获取,如 Semaphore 和 CountDownLatch。

核心思想:

  • 使用CAS操作 控制共享资源的访问
  • 维护一个CLH(FIFO)等待队列 存放获取资源失败的线程
  • 使用模板方法模式 让子类实现具体的获取/释放逻辑

1.2 AQS的设计目标 ​

AQS 的设计目标主要包括:

  1. 简化同步器的开发
    • 线程同步涉及队列管理、阻塞/唤醒、CAS操作等,AQS 封装了底层逻辑,只需要实现核心方法(tryAcquire、tryRelease等)即可自定义同步器。
  2. 支持两种同步模式
    • 独占模式(Exclusive):一次只能被一个线程获取,如 ReentrantLock。
    • 共享模式(Shared):多个线程可同时获取,如 Semaphore、CountDownLatch。
  3. 基于CLH(FIFO)队列 实现同步等待队列
    • 线程获取资源失败后会被添加到AQS的队列,按照**先进先出(FIFO)**的方式调度。
  4. 高效的线程阻塞和唤醒机制
    • AQS 通过 LockSupport.park/unpark 高效管理线程阻塞和唤醒,减少CPU空转,提高性能。
  5. 可扩展性 & 可复用性
    • AQS 不直接实现锁逻辑,而是提供了 通用的同步框架,具体的同步逻辑由子类(如 ReentrantLock)来实现。

1.3 AQS的应用场景 ​

AQS 作为 JUC 并发工具的核心,几乎所有的同步类都基于 AQS 实现,包括:

工具类模式作用
ReentrantLock独占模式可重入锁,支持公平/非公平模式
CountDownLatch共享模式计数器,等待多个线程执行完毕
Semaphore共享模式计数信号量,控制并发线程数
ReadWriteLock读-共享,写-独占读写分离,提高并发性能
CyclicBarrier计数同步让一组线程达到某个点后再继续执行
Phaser多阶段栅栏更灵活的CyclicBarrier

2. AQS的核心数据结构 ​

2.1 AQS加锁流程 ​

AQS 的加锁流程主要围绕 同步队列(CLH 队列) 和 条件队列(Condition Queue) 进行,核心机制包括:

  1. 多个线程竞争锁,只有一个线程成功,其他线程进入 同步队列 排队。
  2. 持有锁的线程可以释放锁,进入 条件队列 等待某个条件成立时被唤醒。
  3. 线程唤醒后,重新进入 同步队列,按照 FIFO 规则尝试获取锁。

image-20250311142710059

2.2 exclusiveOwnerThread变量 ​

首先AQS继承自AbstractOwnableSynchronizer,其实是为了记录哪个线程正在占用锁,适用于 ReentrantLock 这种独占锁(不适用于 Semaphore、CountDownLatch 这类共享锁)。。

java
public abstract class AbstractOwnableSynchronizer {

    private transient Thread exclusiveOwnerThread;

    // 设置占用锁的线程
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

2.2 State变量(同步状态) ​

java
private volatile int state;

AQS 内部维护了一个 volatile int state 变量,用于表示当前同步资源的状态。

  • state 变量的值由子类(如 ReentrantLock)决定具体含义。
  • 通过 CAS(compareAndSetState) 操作确保对 state 的修改是线程安全的。
  • state 变量的操作主要通过以下方法进行:

state 变量的操作方法

方法作用
getState()获取当前同步状态
setState(int newState)设置同步状态
compareAndSetState(int expect, int update)CAS 方式修改 state(保证原子性)

示例:ReentrantLock 如何使用 state

  • 初始值 state = 0:表示锁未被占用。
  • 线程 A 获取锁(state +1),如果是可重入锁,每重入一次 state +1。
  • 线程 A 释放锁(state -1),如果 state = 0,表示锁完全释放。

2.3 Node 节点结构 ​

在 AQS(AbstractQueuedSynchronizer)中,Node 是 同步队列(CLH 队列) 和 条件队列(Condition Queue) 的核心数据结构。它表示一个线程在等待获取锁的状态,并维护指向前后节点的指针。

虽然同步队列和条件队列都是由Node节点组成的,但是同步队列中是使用prev和next组成双向链表,nextWaiter只用来表示是共享模式还是排他模式。条件队列没有使用到Node中prev和next属性,而是使用nextWaiter组成单链表。

java
// 继承自AbstractOwnableSynchronizer,为了记录哪个线程占用锁
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
  
    // 同步状态,0表示无锁,每次加锁+1,释放锁-1
    private volatile int state;

    // 同步队列的头尾节点
    private transient volatile Node head;
    private transient volatile Node tail;

    // Node节点,用来包装线程,放到队列中
    static final class Node {
        // 节点中的线程
        volatile Thread thread;

        // 节点状态
        volatile int waitStatus;

        // 同步队列的前驱节点和后继节点
        volatile Node prev;
        volatile Node next;

        // 条件队列的后继节点
        Node nextWaiter;
    }

    // 条件队列
    public class ConditionObject implements Condition {
        // 条件队列的头尾节点
        private transient Node firstWaiter;
        private transient Node lastWaiter;
    }
}

Node 的关键状态

状态说明
SIGNAL (-1)线程需要被唤醒,表示前驱节点释放锁后,需要唤醒后继线程。
CANCELLED (1)线程因超时或中断放弃了锁,会被移除队列。
CONDITION (-2)线程在 Condition.await() 时进入此状态,等待被 signal() 唤醒。
PROPAGATE (-3)共享模式下的传播状态,表示当前节点释放锁时,需要唤醒多个线程。
0默认状态,没有特殊含义。

Node 在独占/共享模式下的作用

  • 独占模式(Exclusive):线程依次获取锁,如 ReentrantLock。
  • 共享模式(Shared):多个线程同时获取锁,如 Semaphore。

2.4 条件队列(Condition Queue) ​

概念: 条件队列用于线程等待某些条件时的调度。线程通过 await() 进入条件队列,直到条件满足后通过 signal() 或 signalAll() 唤醒,进入同步队列继续争夺锁。

java
// 条件队列
 public class ConditionObject implements Condition {
        // 条件队列的头尾节点
        private transient Node firstWaiter;
        private transient Node lastWaiter;
 }

image-20250311145421662

2.5 FIFO等待队列(CLH队列) ​

概念: 在 AQS 中,FIFO 等待队列是一种 先进先出(FIFO) 的队列结构,通常用于管理等待获取锁的线程。AQS 使用了一种特殊的队列结构——CLH(Craig, Landin, and Hagersten)队列,来确保线程按顺序获取锁。

CLH队列是一个双向链表结构,用于保证多个线程按顺序获取锁。每个线程都会有一个对应的 Node 节点,且队列的头部表示当前持有锁的线程,队尾表示等待锁的线程。

image-20250311145717188

3. AQS的模版方法模式 ​

AQS(AbstractQueuedSynchronizer)提供了一个通用框架来管理线程的同步和锁的访问,支持独占模式和共享模式两种访问资源的方式。AQS本身定义了加锁、释放锁的流程,但并未实现具体的加锁和释放锁的细节,而是通过模板方法模式留给子类(如 ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier 等)去实现具体的行为。

3.1 独占模式和共享模式 ​

  • 独占模式:只有一个线程能够持有锁,其他线程只能等待。
  • 共享模式:多个线程可以同时持有资源,通常用于读写锁等场景。

AQS通过独占模式和共享模式的不同方法来区分这两种锁的行为,下面是每种模式下的方法:


3.2 独占模式方法 ​

独占模式是指一个时间点只有一个线程能持有锁,其相关方法包括:

  • acquire():用于加锁,尝试获取独占锁,若获取失败,则当前线程会进入等待队列。
  • acquireInterruptibly():加锁,但允许中断。如果线程在等待期间被中断,会抛出 InterruptedException。
  • tryAcquireNanos(int arg, long nanosTimeout):尝试在一定时间内获取锁,如果获取失败,则等待指定的时间后返回。
  • release():释放独占锁,使得其他线程能够获得该锁。

3.3 共享模式方法 ​

共享模式是指多个线程可以同时持有资源的锁,这些线程通常处于读取状态,常见于读写锁等场景。共享模式的方法包括:

  • acquireShared():尝试获取共享锁,允许多个线程同时获得锁。
  • acquireSharedInterruptibly():加共享锁,但允许中断。如果线程在等待期间被中断,会抛出 InterruptedException。
  • tryAcquireSharedNanos(int arg, long nanosTimeout):尝试在一定时间内获取共享锁,如果失败则等待指定时间。
  • releaseShared():释放共享锁,通常是解除对多个线程持有的锁的限制。

3.4 AQS中的抽象方法 ​

AQS定义了一些抽象方法,具体的加锁、释放锁逻辑留给子类去实现,这些方法就是模板方法模式的核心。子类需要根据自己的业务需求实现这些方法。AQS提供了以下抽象方法:

  • tryAcquire(int arg):尝试获取独占锁,子类实现具体的获取锁逻辑。
  • tryRelease(int arg):释放独占锁,子类实现具体的释放锁逻辑。
  • tryAcquireShared(int arg):尝试获取共享锁,子类实现具体的共享锁获取逻辑。
  • tryReleaseShared(int arg):释放共享锁,子类实现具体的释放共享锁的逻辑。
  • isHeldExclusively():判断当前线程是否持有独占锁。

这些方法并没有在AQS中实现,具体的实现交给了继承AQS的子类,子类可以根据自己的需求来实现具体的加锁和释放锁逻辑。


3.5 模板方法模式 ​

AQS通过定义一系列的模板方法来规范化加锁和释放锁的流程,这种方式使得AQS本身可以处理线程的排队和等待等公共操作,而具体的锁获取和释放操作则由子类实现。

设计模式:模板方法模式,父类(AQS)定义了加锁、释放锁的整体框架,并在需要的时候调用子类实现的具体方法(如 tryAcquire()、tryRelease()、tryAcquireShared() 等)。这样可以保证AQS具有通用性,同时又能提供灵活性,适应不同的同步需求。

我来结合源码讲解AQS (AbstractQueuedSynchronizer) 的工作原理。

3. AQS的工作原理 ​

AQS是Java并发包中锁和同步器的基础框架,它通过一个整型状态变量state和一个FIFO等待队列来实现锁的获取与释放。

3.1 获取锁(acquire) ​

acquire方法是独占模式获取锁的入口:

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

执行流程:

  1. 首先调用tryAcquire尝试获取锁,这个方法需要子类实现
  2. 如果获取失败,创建一个独占模式的节点并加入等待队列
  3. 调用acquireQueued使线程在队列中等待锁
  4. 如果等待过程中被中断,最后调用selfInterrupt恢复中断状态

addWaiter用于将当前线程包装成Node添加到队列:

java
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方法使线程在队列中等待获取锁:

java
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);
    }
}

3.2 释放锁(release) ​

release方法用于独占模式下的锁释放:

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

执行流程:

  1. 调用tryRelease尝试释放锁,这个方法需要子类实现
  2. 如果释放成功,检查头节点的等待状态
  3. 如果需要,唤醒后继节点的线程

unparkSuccessor用于唤醒头节点的后继节点:

java
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    // 将头节点的状态设为0
    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);
}

3.3 共享模式和独占模式 ​

AQS支持两种同步模式:独占模式和共享模式。

独占模式:同一时刻只有一个线程能持有锁,如ReentrantLock。

共享模式:同一时刻可以有多个线程持有锁,如Semaphore、CountDownLatch。

共享模式获取锁: ​

java
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

doAcquireShared的关键点:

java
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // 判断是否应该阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

共享模式的一个重要特点是锁的传播,setHeadAndPropagate方法:

java
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head;
    setHead(node);
    
    // 如果还有剩余的共享资源,或头节点等待状态表明要传播
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        // 如果后继节点是共享模式,或者为null(不确定模式),继续唤醒
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

共享模式释放锁: ​

java
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

doReleaseShared确保所有等待在共享锁上的线程都能被唤醒:

java
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // 重新开始循环
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // 重新开始循环
        }
        // 如果头节点没有变化,退出循环
        if (h == head)                   
            break;
    }
}

3.4 可中断与超时支持 ​

AQS支持可中断和超时获取锁的方式。

可中断获取锁: ​

java
public final void acquireInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

doAcquireInterruptibly的关键在于当检测到中断时直接抛出异常:

java
private void doAcquireInterruptibly(int arg) throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                // 检测到中断直接抛出异常
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

超时获取锁: ​

java
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

doAcquireNanos增加了超时检查:

java
private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    // 计算超时时间点
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            // 计算剩余等待时间
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                return false;
            // 只有需要等待的时间足够长时才阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

这就是AQS的核心工作原理,它通过这些机制提供了一套灵活的同步框架,各种同步器如ReentrantLock、Semaphore、CountDownLatch等都是基于这个框架实现的。

4. 经典问题与面试高频考点 ​

4.1 AQS的底层实现原理 ​

AQS (AbstractQueuedSynchronizer) 的底层实现原理可以总结为以下几点:

  1. 状态变量:AQS维护一个volatile整型变量state作为同步状态,通过CAS操作和volatile语义保证其可见性和原子性。

  2. FIFO队列:使用一个基于双向链表实现的CLH队列来管理等待线程。当线程获取锁失败时,会被封装成Node加入队列尾部。

  3. Node结构:队列节点包含线程引用、等待状态(waitStatus)、前驱和后继指针。waitStatus有多种值:

    • CANCELLED (1):线程取消
    • SIGNAL (-1):后继节点需要被唤醒
    • CONDITION (-2):节点在条件队列中
    • PROPAGATE (-3):共享锁需要向后传播
    • 0:初始状态
  4. 模板方法模式:AQS定义了框架结构,但具体的锁获取与释放逻辑由子类实现:

    java
    protected boolean tryAcquire(int arg) { 
        throw new UnsupportedOperationException(); 
    }
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
  5. 线程阻塞与唤醒:使用LockSupport.park()和LockSupport.unpark()实现线程的阻塞与唤醒,而不是synchronized的monitor机制。

  6. CAS操作:使用Unsafe类提供的CAS方法进行非阻塞的原子操作,如修改state、队列节点状态等。

4.2 公平锁与非公平锁的区别 ​

公平锁和非公平锁的主要区别在于获取锁的顺序策略:

  1. 定义区别:

    • 公平锁:严格按照线程在队列中的FIFO顺序获取锁
    • 非公平锁:允许新到达的线程在入队前尝试获取锁,可能"插队"
  2. 实现区别(以ReentrantLock为例):

    java
    // 非公平锁的tryAcquire实现
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {  // 直接尝试获取锁,不检查队列
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 其他代码...
    }
    
    // 公平锁的tryAcquire实现
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&  // 检查队列中是否有前驱节点
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 其他代码...
    }
  3. 性能对比:

    • 非公平锁:吞吐量更高,但可能导致某些线程长时间等待(饥饿)
    • 公平锁:等待时间更可预测,但有额外开销(需要检查队列)
  4. 适用场景:

    • 非公平锁:高并发、追求吞吐量的场景
    • 公平锁:对线程等待公平性有要求的场景

4.3 自旋锁与阻塞锁的对比 ​

自旋锁和阻塞锁代表了两种不同的线程等待策略:

  1. 工作原理:

    • 自旋锁:线程通过循环检查锁状态,不放弃CPU时间片
    • 阻塞锁:线程被挂起(park),放弃CPU时间片
  2. 适用场景:

    • 自旋锁:适合锁竞争不激烈、锁持有时间短的场景
    • 阻塞锁:适合锁竞争激烈、锁持有时间长的场景
  3. 优缺点对比:

    特性自旋锁阻塞锁
    CPU消耗高(一直占用CPU)低(放弃CPU时间片)
    线程切换无上下文切换有上下文切换开销
    响应速度快(无需唤醒)慢(需要唤醒过程)
    适用锁持有时间短长
    系统负载影响高负载下性能下降明显高负载下相对稳定
  4. AQS中的应用:AQS结合了两者优势,使用"适应性自旋"策略:

    • 先进行一定次数自旋尝试
    • 自旋失败后再进入阻塞状态
    • 自旋次数会根据历史成功率动态调整

4.4 独占模式 vs 共享模式的应用场景 ​

AQS支持两种同步模式,它们适用于不同的场景:

  1. 独占模式(Exclusive):
    • 定义:同一时刻只允许一个线程访问资源
    • 实现方法:重写tryAcquire和tryRelease
    • 典型应用:
      • ReentrantLock:独占可重入锁
      • WriteLock:读写锁中的写锁
      • ThreadPoolExecutor的Worker锁
    • 应用场景:需要互斥访问的场景,如数据修改操作
  2. 共享模式(Shared):
    • 定义:同一时刻允许多个线程同时访问资源
    • 实现方法:重写tryAcquireShared和tryReleaseShared
    • 典型应用:
      • Semaphore:信号量,控制并发线程数
      • CountDownLatch:倒计时门闩,等待多个操作完成
      • ReadLock:读写锁中的读锁
      • CyclicBarrier:循环栅栏(内部使用ReentrantLock+Condition)
    • 应用场景:允许多个线程并发访问的场景,如读操作、资源池限流
  3. 选择依据:
    • 资源是否允许并发访问
    • 是否需要控制并发线程数量
    • 操作是读密集型还是写密集型

4.5 如何自定义一个同步器? ​

自定义同步器需要继承AQS并实现相应的方法:

  1. 选择模式:确定是独占模式还是共享模式(或两者都支持)
  2. 实现基本方法:
    • 独占模式:实现tryAcquire和tryRelease
    • 共享模式:实现tryAcquireShared和tryReleaseShared
    • 可根据需要同时实现两种模式
  3. 定义状态含义:明确AQS中state变量的含义,如:
    • 锁计数(如ReentrantLock)
    • 可用许可证数量(如Semaphore)
    • 等待线程数(如CountDownLatch)
  4. 设计API:包装AQS方法,提供友好的接口
  5. 示例:简单的二元信号量实现:
java
public class BinarySemaphore {
    private static final class Sync extends AbstractQueuedSynchronizer {
        // 是否可获取,state=1表示可获取,state=0表示已被获取
        protected boolean isAvailable() {
            return getState() == 1;
        }
        
        // 尝试获取:将state从1改为0
        protected boolean tryAcquire(int acquires) {
            if (compareAndSetState(1, 0)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        
        // 释放:将state从0改为1
        protected boolean tryRelease(int releases) {
            if (getState() == 0) {
                if (getExclusiveOwnerThread() != Thread.currentThread())
                    throw new IllegalMonitorStateException();
                setExclusiveOwnerThread(null);
                setState(1);
                return true;
            }
            return false;
        }
    }
    
    private final Sync sync = new Sync();
    
    // 构造函数,初始为可用状态
    public BinarySemaphore() {
        sync.setState(1);
    }
    
    // 获取许可
    public void acquire() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    
    // 释放许可
    public void release() {
        sync.release(1);
    }
    
    // 尝试获取许可(非阻塞)
    public boolean tryAcquire() {
        return sync.tryAcquire(1);
    }
    
    // 当前是否可获取
    public boolean isAvailable() {
        return sync.isAvailable();
    }
}

这个简单的二元信号量示例展示了如何基于AQS创建自定义同步器。在实际应用中,可以根据需求增加超时控制、公平性选项等更多功能。

自定义同步器的关键是正确理解和使用state变量,以及选择合适的同步模式。通过继承AQS,可以轻松获得线程安全、可靠的队列管理和线程调度能力,而不必关心底层的实现细节。

DISCUSSION

评论区

欢迎分享你的思考、补充或问题。

基于 VitePress 搭建