揭秘AQS原理,手把手带你写ReentrantLock核心实现
2023-01-13 18:49:20
AQS:深入理解抽象队列同步器
在并发编程领域,协调多个线程对共享资源的访问至关重要。AbstractQueuedSynchronizer (AQS) 是一种抽象队列同步器,提供了实现各种同步原语的基石。它巧妙地结合了 CLH 队列和 TAS 锁的优点,创造了一种既高效又可靠的同步机制。
CLH 队列:无锁队列
CLH 队列是一种基于 Compare And Swap (CAS) 操作的无锁队列。它允许线程在不阻塞的情况下并发地向队列中添加和删除元素。然而,它也容易出现 ABA 问题,即队列中的一个元素可能被修改多次,但其值始终保持不变。
TAS 锁:自旋锁
TAS 锁(Test And Set Lock)是一种自旋锁,它使用 CAS 操作来获取和释放锁。当一个线程尝试获取锁时,它会不断地检查锁的状态,直到锁处于解锁状态。这种自旋等待的机制虽然简单,但可能会导致处理器资源的浪费。
AQS 的原理
AQS 巧妙地结合了 CLH 队列和 TAS 锁的特性,创建了一种高效且无 ABA 问题的同步机制。它使用一个队列来存储等待获取锁的线程,同时利用 CAS 操作来实现无锁的队列操作。
ReentrantLock:AQS 的具体实现
ReentrantLock 是 Java 并发编程中广泛使用的可重入锁,它基于 AQS 实现。ReentrantLock 的核心数据结构是一个表示锁状态的整型变量。它提供了 lock()、unlock() 和 tryLock() 等方法,允许线程获取、释放和尝试获取锁。
AQS 与 ReentrantLock 的区别
AQS 是一个抽象类,提供各种同步原语的支持,而 ReentrantLock 是 AQS 的具体实现,仅提供可重入锁的支持。此外,AQS 使用队列作为核心数据结构,而 ReentrantLock 使用整型变量。
手把手编写 ReentrantLock
让我们用 Java 手把手编写 ReentrantLock 的核心实现:
public class ReentrantLock {
private int state;
public ReentrantLock() {
state = 0;
}
public void lock() {
while (true) {
int expected = 0;
int updated = 1;
if (compareAndSet(state, expected, updated)) {
return;
}
}
}
public void unlock() {
int expected = 1;
int updated = 0;
if (compareAndSet(state, expected, updated)) {
return;
}
}
public boolean tryLock() {
int expected = 0;
int updated = 1;
return compareAndSet(state, expected, updated);
}
private boolean compareAndSet(int expected, int updated) {
synchronized (this) {
if (state == expected) {
state = updated;
return true;
}
return false;
}
}
}
常见问题解答
1. AQS 和 ReentrantLock 有什么区别?
AQS 提供了各种同步原语的支持,而 ReentrantLock 仅支持可重入锁。
2. CLH 队列和 TAS 锁有什么优势和劣势?
CLH 队列是无锁的,但容易出现 ABA 问题;TAS 锁简单,但会导致自旋等待。
3. AQS 如何结合 CLH 队列和 TAS 锁?
AQS 使用 CLH 队列存储等待线程,并使用 TAS 锁实现无锁的队列操作。
4. ReentrantLock 如何基于 AQS 实现?
ReentrantLock 将 AQS 的队列替换为一个整型变量,表示锁的状态。
5. 为什么使用 CAS 操作?
CAS 操作提供了一种无锁的方式来更新共享变量,避免了阻塞和竞争条件。
结论
AQS 是并发编程中的一个基础性概念,它提供了高效且可靠的同步机制。ReentrantLock 作为 AQS 的具体实现,广泛用于 Java 并发编程中。通过理解 AQS 的原理和 ReentrantLock 的实现,我们可以更深入地了解并发编程的复杂性和优雅。