深入解析Java AQS:源码剖析与典型实现

126 阅读5分钟

AbstractQueuedSynchronizer (AQS) 是Java并发包(java.util.concurrent)的核心基础设施,它为锁(Lock)、信号量(Semaphore)、栅栏(CyclicBarrier)、闭锁(CountDownLatch)等多种同步器提供了一个统一的框架。通过解析AQS的源码并深入分析其典型实现,我们可以更好地理解Java并发机制的工作原理。

1. AQS 基础架构与源码分析

1.1 AQS 的核心成员

AQS 的核心包括以下几个关键成员变量和方法:

// 用于表示同步状态的变量
private volatile int state;

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

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

// 尝试获取独占锁,由子类实现
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

// 尝试释放独占锁,由子类实现
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

// 尝试获取共享锁,由子类实现
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

// 尝试释放共享锁,由子类实现
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

state 是AQS的核心变量,它用于表示同步状态。headtail 是一个双向链表结构,用于管理等待的线程。

1.2 线程排队机制

AQS 使用一个双向链表作为同步队列,这个链表的节点是 Node,每个节点对应一个线程。节点中的关键字段包括:

static final class Node {
    static final Node SHARED = new Node();
    static final Node EXCLUSIVE = null;

    static final int CANCELLED = 1;
    static final int SIGNAL = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;

    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter;
}

waitStatus 用于表示线程的等待状态,prevnext 指向前后节点,形成一个双向链表结构。thread 存放当前节点对应的线程引用。

1.3 获取与释放同步状态

AQS 提供了 acquirerelease 方法来进行同步状态的获取与释放:

  • acquire 方法的核心逻辑是尝试获取锁(通过调用 tryAcquire),如果获取失败,则将当前线程加入同步队列中,直到获取成功。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
  • release 方法则是释放同步状态(调用 tryRelease),并唤醒同步队列中的下一个节点。

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

2. ReentrantLock 的源码与实现

ReentrantLock 是AQS的一个典型实现,它通过AQS的独占模式实现了可重入锁。ReentrantLock 分为公平锁和非公平锁,分别对应 FairSyncNonfairSync 内部类。

2.1 非公平锁的实现

NonfairSyncReentrantLock 的默认实现,它允许“插队”,提高了性能:

static final class NonfairSync extends Sync {
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

lock() 方法首先尝试使用 CAS 直接获取锁,如果失败则通过 acquire(1) 进入 AQS 的获取逻辑。tryAcquire 方法用于尝试获取锁,非公平锁的获取并不考虑队列中的顺序,而是直接尝试抢占。

2.2 公平锁的实现

FairSync 则严格按照FIFO顺序分配锁,避免“插队”:

static final class FairSync extends Sync {
    final void lock() {
        acquire(1);
    }

    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;
            }
        } else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

FairSynctryAcquire 方法在获取锁前,会先检查队列中是否有等待的线程(hasQueuedPredecessors),确保锁的公平性。

3. Semaphore 的源码与实现

Semaphore 是基于AQS共享模式实现的信号量,允许多个线程同时访问资源。Semaphore 可以控制同时访问的线程数。

3.1 信号量的实现

Semaphore 的主要方法是 acquirerelease

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

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

acquire 方法通过 acquireSharedInterruptibly 获取共享资源,而 release 方法通过 releaseShared 释放资源。

3.2 共享模式的实现

tryAcquireSharedtryReleaseSharedSemaphore 的核心逻辑:

protected int tryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 || compareAndSetState(available, remaining))
            return remaining;
    }
}

protected boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (compareAndSetState(current, next))
            return true;
    }
}

tryAcquireShared 方法在循环中通过CAS尝试减少信号量,当信号量足够时返回剩余的资源数。tryReleaseShared 方法则是增加信号量。

4. CountDownLatch 的源码与实现

CountDownLatch 是一个同步工具类,用于协调一组线程的执行,直到计数器减到零。

4.1 闭锁的实现

CountDownLatch 主要依赖于 awaitcountDown 方法:

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

public void countDown() {
    sync.releaseShared(1);
}

await 方法会等待计数器减到零,而 countDown 方法则是减少计数器。

4.2 共享模式的实现

CountDownLatchSync 类实现了 tryAcquireSharedtryReleaseShared 方法:

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

protected boolean tryReleaseShared(int releases) {
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c - 1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

tryAcquireSharedawait 时检查计数器是否为零,如果为零则可以通过。tryReleaseShared 则减少计数器,当减到零时唤醒等待的线程。

5. CyclicBarrier 的源码与实现

CyclicBarrier 是一种同步辅助类,它允许一组线程互相等待,直到到达一个共同的屏障点。不同于 CountDownLatchCyclicBarrier 可以重用。

5.1 栅栏的实现

CyclicBarrier 的核心方法是 await

public int await() throws InterruptedException, BrokenBarrierException {
    return dowait(false, 0L);
}

5.2 屏障的管理

CyclicBarrier 使用了一个计数器和一个屏障点来管理线程的等待和唤醒:

private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        final Generation g = generation;

        if (g.broken)
            throw new BrokenBarrierException();

        int index = --count;
        if (index == 0) {  // tripped
            nextGeneration();
            return 0;
        }

        for (;;) {
            try {
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                breakBarrier();
                throw ie;
            }
            if (g.broken)
                throw new BrokenBarrierException();
            if (g != generation)
                return index;
        }
    } finally {
        lock.unlock();
    }
}

当最后一个线程到达时,屏障会重置,并唤醒所有等待的线程。否则,线程进入等待状态,直到屏障被触发。

6. 总结

通过深入剖析AQS的源码及其典型实现如 ReentrantLockSemaphoreCountDownLatchCyclicBarrier,我们可以看到AQS如何通过一种通用的机制来实现各种复杂的同步需求。AQS 的设计极其灵活和高效,但其复杂性也要求开发者具备较深的并发编程知识。通过掌握AQS的工作原理,我们可以更好地应用和扩展Java的并发工具,更有效地解决实际中的并发问题。