返回

解读 AQS,探寻并行编程的艺术

后端

AQS:Java并发编程的基石

在 Java 的早期时代,并发编程主要依赖于内置的 synchronizedwait()/notify() 方法。然而,这些底层机制的使用灵活性不足,难以满足不断增长的复杂并发需求。

为了解决这一难题,Java 5 引入了 AQS (AbstractQueuedSynchronizer) 。AQS 是一个抽象类,提供了一系列原语,使开发者能够轻松构建自己的同步器。这些同步器可以控制资源访问、协调线程协作,并确保程序的正确性和一致性。

AQS 的原理

AQS 的核心思想是使用一个共享队列来管理线程的等待和唤醒。当一个线程需要访问受保护的资源时,它会首先尝试获取资源的锁。如果锁已被其他线程持有,当前线程将被挂起,并加入到队列中等待。

当持有锁的线程释放锁时,AQS 会唤醒队列中等待的最前面的线程,使其能够获取锁并继续执行。这种机制确保了资源的互斥访问,防止多个线程同时操作同一个资源,导致数据不一致。

AQS 的数据结构

AQS 使用一个名为 CLH 队列的数据结构来管理等待的线程。CLH 队列是一种链表结构,每个节点代表一个等待的线程。当一个线程需要等待时,它会创建一个新的节点并将其添加到队列的末尾。

当 AQS 唤醒一个线程时,它会从队列的头部取出该节点,并将锁交给该线程。这种机制确保了等待队列的先进先出 (FIFO) 特性,保证了线程的公平性。

AQS 对 CLH 队列锁的改进

在 CLH 队列锁的基础上,AQS 做了一些改进,使其更加高效和可靠。

  • 自旋锁: 自旋锁是一种忙等待机制,当一个线程试图获取锁时,它会不断地检查锁的状态,直到锁被释放。这种机制可以避免线程切换带来的性能开销,提高了锁获取的效率。
  • 自定义公平策略: AQS 允许开发者指定自定义的公平策略。在默认情况下,AQS 使用公平锁,即线程按照进入队列的顺序获取锁。然而,开发者也可以选择使用非公平锁,在这种情况下,线程获取锁的顺序是不确定的。

AQS 的核心方法

AQS 提供了几个核心的方法,允许开发者轻松构建自己的同步器。这些方法包括:

  • acquire(): 尝试获取锁。如果锁已被其他线程持有,当前线程将被挂起,并加入到队列中等待。
  • release(): 释放锁。当持有锁的线程释放锁时,AQS 会唤醒队列中等待的最前面的线程。
  • tryAcquire(): 尝试获取锁,但不阻塞当前线程。如果锁已被其他线程持有,则该方法将立即返回 false
  • hasQueuedThreads(): 检查是否有线程在等待获取锁。
  • getQueueLength(): 获取等待获取锁的线程数。

代码示例:实现一个简单的锁

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class SimpleLock extends AbstractQueuedSynchronizer {

    // 定义锁的状态,初始值为 0,表示未加锁
    private static final int UNLOCKED = 0;
    // 定义锁的状态,初始值为 1,表示已加锁
    private static final int LOCKED = 1;

    @Override
    protected boolean tryAcquire(int acquires) {
        // 如果当前锁处于未加锁状态,则获取锁并返回 true
        if (compareAndSetState(UNLOCKED, LOCKED)) {
            return true;
        }
        // 否则,返回 false
        return false;
    }

    @Override
    protected boolean tryRelease(int releases) {
        // 如果当前锁处于已加锁状态,则释放锁并返回 true
        if (compareAndSetState(LOCKED, UNLOCKED)) {
            return true;
        }
        // 否则,返回 false
        return false;
    }

    public void lock() {
        // 尝试获取锁,如果失败,则阻塞当前线程
        acquire(1);
    }

    public void unlock() {
        // 释放锁
        release(1);
    }
}

常见问题解答

  1. AQS 和 synchronized 有什么区别?
    AQS 是一个更底层的抽象,它提供了更多的灵活性,允许开发者构建自定义的同步器。相比之下,synchronized 是一个内置的语法糖,它使用 monitorentermonitorexit 指令来实现同步。

  2. AQS 的性能如何?
    AQS 的性能非常高,它使用自旋锁和先进先出 (FIFO) 队列来实现高效的锁获取和释放。

  3. AQS 可以用于哪些场景?
    AQS 可以用于各种场景,包括线程同步、资源管理、读写锁和条件变量等。

  4. 如何使用 AQS 构建自定义同步器?
    要使用 AQS 构建自定义同步器,开发者需要扩展 AbstractQueuedSynchronizer 类并实现 tryAcquire()tryRelease() 方法来定义同步器的行为。

  5. AQS 在 Java 中的哪些类中被使用?
    AQS 在 Java 中的许多类中被使用,包括 ReentrantLockSemaphoreCountDownLatch 等。