AQS: Java 并发基础框架

230 阅读8分钟

AbstractQueuedSynchronizer (AQS) 是 Java 并发包(java.util.concurrent)中的一个基础框架,用于构建线程同步工具如锁、信号量、屏障等。AQS 提供了一种基于 FIFO 队列的等待机制,可以处理线程的阻塞和唤醒,帮助开发者实现各种复杂的同步器。了解 AQS 的设计和工作原理有助于我们深入理解 Java 并发编程。

主要特性和概念

1. 独占模式(Exclusive Mode)

在独占模式下,资源只能被一个线程占有。例如,ReentrantLock 就是基于独占模式实现的。使用独占模式的主要方法有:

  • acquire(int arg):独占模式的获取方法,如果不能立即获取,则进入等待队列。
  • release(int arg):独占模式的释放方法,释放资源并唤醒等待队列中的一个线程。

2. 共享模式(Shared Mode)

在共享模式下,资源可以被多个线程同时占有。例如,Semaphore 和 CountDownLatch 就是基于共享模式实现的。使用共享模式的主要方法有:

  • acquireShared(int arg):共享模式的获取方法,如果不能立即获取,则进入等待队列。
  • releaseShared(int arg):共享模式的释放方法,释放资源并可能唤醒等待队列中的一个或多个线程。

3. FIFO 等待队列

AQS 通过一个 FIFO 队列管理等待线程,当某个线程无法获取资源时,它会进入这个队列等待,并在资源变得可用时被唤醒。每个节点(Node)代表一个等待的线程。

4. 状态字段

AQS 使用一个整数(state)字段来表示同步状态。子类通过重写 tryAcquire(int arg)tryRelease(int arg)tryAcquireShared(int arg) 和 tryReleaseShared(int arg) 等方法来定义具体的同步语义。

关键方法

以下是 AQS 中几个核心方法及其作用:

  • acquire(int arg):尝试以独占模式获取资源。如果获取失败则进入等待队列,直到资源可用再次尝试获取。
  • release(int arg):以独占模式释放资源,释放成功后将唤醒等待队列中的一个线程。
  • acquireShared(int arg):尝试以共享模式获取资源。如果获取失败则进入等待队列,直到资源可用再次尝试获取。
  • releaseShared(int arg):以共享模式释放资源,释放后可能唤醒等待队列中的多个线程。
  • tryAcquire(int arg):自定义独占模式的资源获取逻辑。
  • tryRelease(int arg):自定义独占模式的资源释放逻辑。
  • tryAcquireShared(int arg):自定义共享模式的资源获取逻辑。
  • tryReleaseShared(int arg):自定义共享模式的资源释放逻辑。
  • isHeldExclusively():查询当前资源是否由当前线程独占。

工作原理

AQS 的工作流程可以大致分为以下几个步骤:

  1. 获取资源:调用 acquire 或 acquireShared 尝试获取资源。

    • 如果资源可用,则更新状态字段 state 并退出。
    • 如果资源不可用,将当前线程包装成 Node 节点并加入等待队列。
  2. 进入队列:线程被插入到 FIFO 等待队列中等待唤醒。

    • 使用 ReentrantLock 之类的机制来保证队列的线程安全性,防止多个线程同时操作队列。
  3. 释放资源:调用 release 或 releaseShared 释放资源。

    • 更新状态字段 state
    • 唤醒等待队列中的后继线程,使其尝试再次获取资源。
  4. 唤醒等待线程:当有线程释放资源时,等待队列中的线程依次被唤醒,并尝试重新获取资源。

代码示例

以下是一个基于 AQS 实现的简单独占锁:

java复制代码
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class SimpleExclusiveLock {

    private final Sync sync = new Sync();

    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                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;
        }
    }

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

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

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }
}

小结

Java AQS 是一个强大的框架,通过提供可扩展的同步机制,使开发者能够高效地实现各种同步工具。理解 AQS 的核心概念和工作原理,包括独占模式和共享模式如何通过 FIFO 等待队列管理,状态字段如何影响同步状态,是理解和实现高效并发编程的重要基础。

具体实现工具

Java 并发包(java.util.concurrent)中许多重要的线程同步工具都是基于 AbstractQueuedSynchronizer (AQS) 实现的。以下是一些常见的基于 AQS 实现的线程同步工具及其具体逻辑:

1. ReentrantLock

ReentrantLock 是一种可重入的互斥锁,它有公平锁和非公平锁两种模式。

具体逻辑:

  • 独占模式:使用独占模式(Exclusive Mode)获取锁。
  • 重入:支持可重入,即同一线程可以多次获得同一把锁,每次加锁计数加一,解锁时计数减一,直到计数为零时释放锁。
  • 公平锁:如果设置为公平锁,则等待时间最长的线程优先获取锁。
  • 非公平锁:如果设置为非公平锁,则当前线程会尝试立即获取锁,如果失败则进入等待队列。

核心代码示例:

java复制代码
public class ReentrantLock {
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        abstract void lock();

        @Override
        protected boolean isHeldExclusively() {
            return getState() != 0 && getExclusiveOwnerThread() == Thread.currentThread();
        }

        @Override
        protected boolean tryRelease(int releases) {
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            int c = getState() - releases;
            if (c == 0) {
                setExclusiveOwnerThread(null);
                setState(c);
                return true;
            }
            setState(c);
            return false;
        }
    }

    static final class NonfairSync extends Sync {
        @Override
        void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        
        @Override
        protected boolean tryAcquire(int acquires) {
            return // 非公平锁的获取逻辑
        }
    }

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

        @Override
        protected boolean tryAcquire(int acquires) {
            return // 公平锁的获取逻辑
        }
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

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

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

2. ReentrantReadWriteLock

ReentrantReadWriteLock 是一种读写锁,它允许多个读操作并发进行,但写操作是互斥的,并且读写操作之间也是互斥的。

具体逻辑:

  • 共享模式:用来实现读锁,多个读线程可以同时获取读锁。
  • 独占模式:用来实现写锁,写锁是互斥的,只有一个线程可以获取写锁。
  • 读写互斥:当一个线程持有写锁时,其他线程不能获取读锁或写锁。

核心代码示例:

java复制代码
public class ReentrantReadWriteLock {
    private final ReadLock readerLock;
    private final WriteLock writerLock;
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        // Read lock and write lock logic

        // 共享模式获取
        protected int tryAcquireShared(int acquires) {
            return // 读锁获取逻辑
        }

        // 共享模式释放
        protected boolean tryReleaseShared(int releases) {
            return // 读锁释放逻辑
        }

        // 独占模式获取
        protected boolean tryAcquire(int acquires) {
            return // 写锁获取逻辑
        }

        // 独占模式释放
        protected boolean tryRelease(int releases) {
            return // 写锁释放逻辑
        }
    }

    static final class FairSync extends Sync {
        // 公平策略的同步器实现
    }

    static final class NonfairSync extends Sync {
        // 非公平策略的同步器实现
    }

    public static class ReadLock {
        public void lock() {
            sync.acquireShared(1);
        }

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

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

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

    public ReentrantReadWriteLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
            readerLock = new ReadLock();
            writerLock = new WriteLock();
    }

    public ReadLock readLock() { return readerLock; }
    public WriteLock writeLock() { return writerLock; }
}

3. CountDownLatch

CountDownLatch 是一个同步工具类,用于多个线程等待某些事件的完成。

具体逻辑:

  • 共享模式:用于实现等待器,调用 await 的线程将等待计数器变为零。
  • 计数器:初始计数值由构造函数设置,调用 countDown 会减少计数器,计数器到零时,所有等待的线程会被唤醒。

核心代码示例:

public class CountDownLatch {
    private final Sync sync;

    private static final class Sync extends AbstractQueuedSynchronizer {
        Sync(int count) {
            setState(count);
        }

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

        @Override
        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;
            }
        }
    }

    public CountDownLatch(int count) {
            sync = new Sync(count);
    }

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

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

4. Semaphore

Semaphore 是一种信号量,用于限制某个资源的并发访问数。

具体逻辑:

  • 共享模式:用于实现信号量,调用 acquire 获取一个许可,调用 release 释放一个许可。
  • 计数器:表示当前可用的许可数量。

核心代码示例:

java复制代码
public class Semaphore {
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        Sync(int permits) {
            setState(permits);
        }

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

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

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

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

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

5. CyclicBarrier

CyclicBarrier 是一种同步工具,用于使一组线程相互等待,直到所有线程都到达某个屏障点。

具体逻辑:

  • 独占模式:用于实现屏障,在所有线程到达屏障点之前,调用 await 的线程将被阻塞。
  • 计数器:计数器到零时,所有等待的线程会被唤醒。

核心代码示例:

java复制代码
public class CyclicBarrier {
    private final Sync sync;

    private static class Generation {
        boolean broken = false;
    }

    private final class Sync extends AbstractQueuedSynchronizer {
        private Generation generation = new Generation();

        Sync(int parties) {
            setState(parties);
        }

        int parties() {
            return getState();
        }

        int arriveAndAwaitAdvance() {
            for (;;) {
                int c = getState();
                if (c == 0)
                    return 0;
                int nextc = c - 1;
                if (compareAndSetState(c, nextc)) {
                    if (nextc == 0) {
                        nextGeneration();
                        return 0;
                    } else {
                        for (;;) {
                            try {
                                wait();
                                break;
                            } catch (InterruptedException e) {
                                notifyAll();
                            }
                        }
                        return nextc;
                    }
                }
            }
        }
        
        private void nextGeneration() {
            notifyAll();
            generation = new Generation();
        }
    }

    public CyclicBarrier(int parties) {
        if (parties <= 0) throw new IllegalArgumentException();
        sync = new Sync(parties);
    }

    public int await() throws InterruptedException, BrokenBarrierException {
            return sync.arriveAndAwaitAdvance();
    }
}

总结

基于 AbstractQueuedSynchronizer (AQS) 的这些同步工具通过灵活使用独占(Exclusive)和共享(Shared)两种模式,以及 FIFO 等待队列机制,实现了高效的线程同步。通过理解这些同步工具的具体逻辑,可以更好地设计和优化基于并发的应用程序。