AQS的核心思想是通过一个FIFO等待队列来实现线程的阻塞和唤醒。AQS维护了一个状态,通过获取和释放状态来实现线程的同步操作。在AQS中,定义了两种队列,即Condition队列和等待队列。Condition队列主要用于支持Condition对象,而等待队列则是实现阻塞同步的关键。
AQS的等待队列中的节点被称为Waiter。每个Waiter节点代表一个等待线程,包含了线程的相关信息。当一个线程需要等待条件或锁时,它会创建一个Waiter节点并加入到AQS的等待队列中。当条件满足或锁可用时,AQS会从等待队列中选择一个节点唤醒,使其从阻塞状态恢复到可运行状态。(说人话就是需要锁时加入队列与释放锁以后唤醒节点)
在具体的同步工具中,比如ReentrantLock、Semaphore等,都是通过继承AQS并实现其抽象方法来实现线程的同步和协调。我认为AQS的设计是非常巧妙的,它为我们提供了一种通用的同步机制,使得我们可以更容易地构建复杂的并发工具。"
互斥锁: 在互斥锁中,state通常为0表示锁是可用的,为1表示锁被占用。当线程获取锁时,会尝试将state从0修改为1,如果成功,线程获得了锁
CAS 操作通常包含以下三个参数:
内存位置(通常是一个变量的引用)。
预期值(当前内存位置的值)。
新值(用来替换内存位置的值)。
CAS 操作执行的逻辑是:
比较内存位置的值与预期值。
如果相等,将内存位置的值更新为新值。
如果不相等,CAS 操作不执行更新,并返回失败。
ABA问题是指在多线程环境下,如果一个值从A变为B,然后再变回A,CAS操作会错误地认为它的值从未改变过。这可能导致意外的结果,特别是在一些需要关注数据的连续性的情况下。
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class CustomLock {
// AQS成员变量
private final Sync sync = new Sync();
// 自定义同步器继承AQS
static class Sync extends AbstractQueuedSynchronizer {
// 在AQS中,最重要的成员变量之一,表示同步状态
// int state;
// 构造函数
Sync() {
setState(0); // 初始状态
}
@Override
protected boolean tryAcquire(int acquires) {
// 重写tryAcquire方法以获取锁
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
}
return false;
}
@Override
protected boolean tryRelease(int releases) {
// 重写tryRelease方法以释放锁
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
int c = getState() - releases;
if (c == 0) {
setExclusiveOwnerThread(null);
}
setState(c);
return true;
}
// 其他自定义方法可以根据需要添加
}
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
}
假设多线程环境,第一个持有锁,其他的CAS操作失败阻塞住,以此放到AQS同步队列中
线程0释放锁,线程1被唤醒获取锁
队头元素出队(就是null结点)