Java并发编程,AQS详解

AbstractQueuedSynchronizer(简称 AQS)是 Java 并发包中一个非常重要的同步框架,它为实现锁和其他同步器提供了一种标准化的方法。AQS 通过内部的状态管理、FIFO 队列以及对线程调度的支持,简化了锁的实现过程。许多标准库中的同步工具(如 ReentrantLockSemaphoreCountDownLatch)都是基于 AQS 实现的。

一、AQS 的基本原理

1. 概念

AQS 是一个抽象类,提供了以下核心功能:

  • 状态管理:AQS 使用一个 volatile int state 来表示同步状态,该状态可以被子类用来表示锁是否被持有、计数器值等信息。
  • 队列机制:AQS 内部维护了一个 FIFO 线程等待队列,当线程尝试获取锁但失败时,它会被加入到这个队列中,并在适当的时候重新尝试获取锁。
  • 独占式和共享式锁:AQS 支持两种模式的锁——独占式(Exclusive Mode)和共享式(Shared Mode)。独占式意味着同一时刻只有一个线程能够持有锁;而共享式则允许多个线程同时持有锁,只要它们的操作不会相互冲突。
  • 条件变量:AQS 还支持条件变量(Condition),允许线程在特定条件下挂起或唤醒其他线程。
2. 实现机制
  • 同步状态的获取与释放:AQS 提供了 tryAcquire(int)tryRelease(int) 方法,子类可以通过重写这两个方法来定义如何获取和释放同步状态。
  • 等待队列操作:AQS 负责管理等待队列上的节点,这些节点代表了等待获取锁的线程。每个节点都包含了指向前后节点的引用,从而形成了双向链表结构。
  • 线程调度:AQS 利用 Java 的内置机制(如 Thread.suspend()Thread.resume() 的替代品)来挂起或恢复线程。对于长时间等待的情况,AQS 会将线程置于阻塞状态,以节省 CPU 资源。

二、AQS 的应用场景

AQS 主要用于构建自定义的同步组件,特别是那些需要复杂同步逻辑的场景:

  • :如 ReentrantLockReentrantReadWriteLock,它们使用 AQS 来实现公平性和非公平性锁的行为。
  • 信号量:如 Semaphore,它可以限制并发访问的数量。
  • 闭锁:如 CountDownLatchCyclicBarrier,它们用来协调多个线程之间的执行顺序。
  • 栅栏:如 Phaser,它可以动态地调整参与者的数量。

三、示例代码

1. 自定义独占锁
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class CustomLock implements Lock {

    private final Sync sync = new Sync();

    private static class Sync extends AbstractQueuedSynchronizer {

        protected boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int releases) {
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        Condition newCondition() {
            return new ConditionObject();
        }
    }

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

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

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }

    // 其他未实现的方法...
}
2. 自定义共享锁
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;

public class CustomSharedLock implements Lock {

    private final Sync sync = new Sync(1);

    private static class Sync extends AbstractQueuedSynchronizer {

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

        int tryAcquireShared(int reduceCount) {
            for (;;) {
                int current = getState();
                int newCount = current - reduceCount;
                if (newCount < 0 || compareAndSetState(current, newCount))
                    return newCount;
            }
        }

        boolean tryReleaseShared(int returnCount) {
            for (;;) {
                int current = getState();
                int newCount = current + returnCount;
                if (compareAndSetState(current, newCount))
                    return true;
            }
        }
    }

    @Override
    public void lock() {
        sync.acquireShared(1);
    }

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

    // 其他未实现的方法...
}
3. 使用条件变量
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BoundedBuffer {

    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    private final Object[] items = new Object[100];
    private int putptr, takeptr, count;

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                notFull.await();
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await();
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

四、相关知识点

1. Java 内存模型(JMM)

Java 内存模型定义了多线程环境下变量的可见性和正确性规则。AQS 通过原子操作和内存屏障确保了在不同平台上的一致行为,这对于实现高效的并发控制至关重要。

2. VarHandle

从 Java 9 开始引入的 VarHandle 是一种更加安全和标准化的方式来执行类似 Unsafe 的操作。虽然 VarHandle 和 AQS 属于不同的层次,但在某些情况下,它们可能会共同作用于同一个应用程序中。例如,VarHandle 可以用来优化 AQS 中的状态更新逻辑。

3. Unsafe

尽管 Unsafe 不推荐用于常规的应用开发,但它确实是 AQS 内部实现的一部分。Unsafe 提供了一些底层操作(如 CAS),这些操作对于实现高效的无锁算法非常重要。不过,随着 Java 版本的演进,越来越多的功能已经被标准化和封装到了更高级别的 API 中,比如 VarHandle

4. ConcurrentHashMap

ConcurrentHashMap 是 Java 并发包中的一个重要组成部分,它实现了线程安全的哈希表。为了保证高并发性能,ConcurrentHashMap 在内部使用了分段锁(Segment)和 AQS 的思想。每个 Segment 都是一个可重入锁,并且负责保护一部分桶数组。这样即使有多个线程同时访问不同的键值对,也不会产生锁争用。

5. ForkJoinPool

ForkJoinPool 是 Java 7 引入的一种并行计算框架,它特别适合递归任务的分解与合并。ForkJoinPool 的工作窃取算法(Work-stealing Algorithm)提高了任务调度效率,减少了线程间的竞争。虽然 ForkJoinPool 并不直接依赖 AQS,但两者之间存在紧密联系,因为它们都致力于提高并发程序的性能。

五、总结

AQS 是 Java 并发编程中一个不可或缺的工具,它不仅简化了锁的实现过程,还为开发者提供了一个强大的基础架构,使得我们可以轻松地创建各种类型的同步组件。理解 AQS 的工作原理及其相关的同步原语(如锁、条件变量等),可以帮助我们在编写复杂的并发程序时更加得心应手。需要注意的是,虽然 AQS 提供了许多便利的功能,但它仍然需要谨慎使用,尤其是在处理复杂的同步逻辑时。此外,随着 Java 版本的不断更新,越来越多的功能已经被标准化和封装到了更高级别的 API 中,因此我们应该优先考虑使用这些 API 来满足日常开发的需求。

你可能感兴趣的:(java,开发语言,jvm)