Java的AbstractQueuedSynchronizer
(AQS) 是Java并发包(java.util.concurrent
)中的一个非常重要的底层同步框架,它用于构建锁、信号量等同步器的基础组件。AQS 提供了一个通用的机制来管理线程之间的同步。通过AQS,Java中的各种同步器如ReentrantLock
、Semaphore
、CountDownLatch
等都是基于它实现的。
AQS的核心设计思想是通过一个 先进先出(FIFO)的等待队列 来管理那些尝试获取同步状态(通常是锁)的线程。当某个线程尝试获取锁资源时,如果资源可用,则立即获取;否则,线程会进入这个等待队列,直到资源可用。
下面是AQS的一些关键概念和设计要点:
1. 同步状态
AQS 使用一个 int
类型的成员变量来表示同步状态(State),这是整个同步控制的核心。同步状态的值表示某个资源的可用性。例如:
在 ReentrantLock
中,state = 0
表示锁没有被占用,而 state = 1
表示锁已被占用。
在 Semaphore
中,state
可以表示剩余的许可数量。
通过AQS的内置方法,可以对这个 state
进行原子操作:
getState()
:获取当前同步状态。
setState(int newState)
:设置同步状态。
compareAndSetState(int expect, int update)
:使用CAS操作设置状态,确保线程安全。
2. 独占模式和共享模式
AQS 支持两种模式的资源获取:
独占模式:在这种模式下,一个线程获取资源时,其他线程必须等待,直到该线程释放资源。例如,ReentrantLock
就是独占模式的实现。
共享模式:多个线程可以同时获取资源。例如,Semaphore
是共享模式的实现,允许多个线程同时获取多个许可。
AQS通过 tryAcquire
、tryRelease
等方法区分独占和共享操作:
tryAcquire(int arg)
和 tryRelease(int arg)
:用于独占模式,分别尝试获取和释放锁。tryAcquireShared(int arg)
和 tryReleaseShared(int arg)
:用于共享模式,分别尝试共享地获取和释放锁。
3. 等待队列
AQS的等待队列是一个 双向链表,当线程尝试获取资源失败时,AQS会将该线程加入到等待队列中,直到资源可用。等待队列中的节点类型是 Node
,每个节点都封装了等待线程的信息。
节点中的一些关键字段:
thread
:表示当前节点对应的线程。
prev
和 next
:分别指向前驱和后继节点。
waitStatus
:表示当前节点的等待状态,有以下几种状态:
SIGNAL
:表示当前节点的线程需要被唤醒。
CANCELLED
:表示线程等待超时或者被中断。
CONDITION
:表示节点在 Condition
队列中等待。
PROPAGATE
:表示下一次获取资源时需要唤醒后继节点。
等待队列的节点按照 FIFO 顺序排列,线程依次排队等待资源。
4. 独占模式下的获取和释放
在独占模式下,AQS 通过以下几个步骤来控制线程的获取和释放资源:
获取资源:当线程尝试获取资源时,首先调用 tryAcquire
方法判断当前资源是否可用。如果资源可用,线程直接获取资源并执行任务。如果资源不可用,线程会被封装为一个 Node
并加入到AQS的等待队列中,线程进入阻塞状态,等待其他线程释放资源。
释放资源:线程执行完任务后,会调用 release
方法释放资源。在 release
方法中,首先调用 tryRelease
方法释放资源。如果等待队列中有线程等待资源,AQS 会通过 unpark
操作唤醒等待队列中的第一个线程。
5. 共享模式下的获取和释放
共享模式的资源获取与独占模式略有不同,主要体现在多个线程可以同时获取资源:
获取资源:当线程尝试共享地获取资源时,首先调用 tryAcquireShared
方法判断资源是否可用。如果资源可用且能够满足多个线程同时获取的条件,线程将继续执行任务。如果资源不足,线程将被封装为 Node
加入到等待队列,等待资源可用。
释放资源:当一个线程释放资源时,AQS会调用 tryReleaseShared
方法释放资源。如果释放后的资源足够多,等待队列中的多个线程可能会被唤醒并继续执行任务。
6. Condition 条件队列
AQS 还为每个同步器提供了 Condition
支持,Condition
用于管理线程的等待和通知操作。通常,Condition
结合 await()
和 signal()
来使用。Condition
的实现也依赖于AQS的等待队列机制,但Condition
有自己独立的等待队列。await()
方法会使当前线程进入条件队列,而 signal()
则负责唤醒条件队列中的线程。
7. 常用的AQS实现类
AQS是一个抽象类,不能直接使用。Java并发包中很多重要的同步器都是基于AQS实现的。常见的实现类包括:
ReentrantLock:基于AQS的独占锁实现。
ReentrantReadWriteLock:支持读写锁的实现。
Semaphore:基于AQS的共享模式实现。
CountDownLatch:基于AQS的计数器实现
8、例子
下面是一个简单的基于AQS实现的独占锁的例子:
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;
public class MyLock implements Lock {
// 自定义同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 是否处于独占模式
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 尝试获取锁
public boolean tryAcquire(int acquires) {
assert acquires == 1; // 只能是1
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 尝试释放锁
protected boolean tryRelease(int releases) {
assert releases == 1; // 只能是1
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
Condition newCondition() { return new ConditionObject(); }
}
// 将操作代理到Sync上
private final Sync sync = new Sync();
// 锁获取方法
public void lock() { sync.acquire(1); }
// 可中断的锁获取方法
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
// 尝试获取锁
public boolean tryLock() { return sync.tryAcquire(1); }
// 超时尝试获取锁
public boolean tryLock(long timeout, java.util.concurrent.TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
// 锁释放方法
public void unlock() { sync.release(1); }
// 提供Condition对象
public java.util.concurrent.locks.Condition newCondition() { return sync.newCondition(); }
}
解释:
Sync类继承自AbstractQueuedSynchronizer,实现了tryAcquire和tryRelease方法,这两个方法分别用于尝试获取和释放锁。
tryAcquire方法通过compareAndSetState原子操作尝试将同步状态从0改为1,如果成功,则当前线程成为独占线程。
tryRelease方法将同步状态从1改为0,并清除独占线程。
MyLock类通过内部持有的Sync对象来实现Lock接口中的方法,从而提供了一个简单的独占锁实现。
标签:java,AQS,队列,编程,获取,线程,等待,资源 From: https://blog.csdn.net/sdg_advance/article/details/142931695