Java并发-AQS和ReentrantLock

AQS与ReentrantLock

什么是AQS?

简单来说AQS就是起到了一个抽象、封装的作用,将一些排队、入队、加锁、中断等方法提供处出来,便于其他相关的JUC锁使用,具体加锁时机、入队时机等都需要实现类自己控制。

常见的实现类有ReentrantLockCountDownLatchSemaphore等等。

AQS的核心机制

  • 状态(State):AQS通过一个volatile类型的整数state来表示同步状态。

子类通过 getState()、setState(int)和 compareAndSetState(int, int)方法来检查和修改该状态
状态可以表示多种含义,例如在 ReentrantLock 中,状态表示锁的重入次数;在 Semaphore 中,状态表示可用的许可数。

image-20241219130734656

  • 队列(Queue):AQS维护了一个FIFO的等待队列,用于管理等待获取同步状态的线程。每个节点(Node)代表一个等待的线程,节点之间通过nextprev指针链接。
1
2
3
4
5
6
7
8
9
10
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread; // 保存等待的线程
Node nextWaiter;
.....
}

当一个线程获取同步状态失败是,它会被添加到等待队列中,并自旋等待或被阻塞,直到前面的线程释放同步状态。

  • 独占模式和共享模式:

独占模式:只有一个线程能获取同步状态,例如 ReentrantLock。
共享模式:多个线程可以同时获取同步状态,例如 Semaphore 和 ReadWriteLock。

ReentrantLock对AQS的使用

ReentrantLock 基于 AQS 实现的方式很简单,定义一个 Sync 继承 AQS 自定义 了tryAcquire、tryRelease、isHeldExclusively 等方法即可实现ReentrantLock和其他独占锁。所以说 AQS 把一切都封装的很好,基于 AQS 可以便于其他相关 JUC 锁的使用。

image-20241219131802456

ReentrantLock 支持公平非公平两种方式。
内部实现依靠一个 state 变量和两个等待队列:

同步队列AND等待队列

利用 CAS 修改 state 来争抢锁。争抢不到则入同步队列等待,同步队列是一个双向链表。

条件 condition 不满足时候则入等待队列等待,为单向链表。

是否是公平锁的区别在于: 线程获取锁时是加入到同步队列尾部还是直接利用 CAS 争抢锁。