解读 AQS,探寻并行编程的艺术
2024-01-11 18:50:33
AQS:Java并发编程的基石
在 Java 的早期时代,并发编程主要依赖于内置的 synchronized
和 wait()/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);
}
}
常见问题解答
-
AQS 和
synchronized
有什么区别?
AQS 是一个更底层的抽象,它提供了更多的灵活性,允许开发者构建自定义的同步器。相比之下,synchronized
是一个内置的语法糖,它使用monitorenter
和monitorexit
指令来实现同步。 -
AQS 的性能如何?
AQS 的性能非常高,它使用自旋锁和先进先出 (FIFO) 队列来实现高效的锁获取和释放。 -
AQS 可以用于哪些场景?
AQS 可以用于各种场景,包括线程同步、资源管理、读写锁和条件变量等。 -
如何使用 AQS 构建自定义同步器?
要使用 AQS 构建自定义同步器,开发者需要扩展AbstractQueuedSynchronizer
类并实现tryAcquire()
和tryRelease()
方法来定义同步器的行为。 -
AQS 在 Java 中的哪些类中被使用?
AQS 在 Java 中的许多类中被使用,包括ReentrantLock
、Semaphore
和CountDownLatch
等。