文章目录
- 一.什么是AQS
- 二.公平锁和非公平锁实现
- 三.公平锁和非公平锁的区别
- 四.小结
一.什么是AQS
AQS,全称 AbstractQueuedSynchronizer,是 Java 中用于构建锁和同步器的一个基础框架类,位于 java.util.concurrent.locks 包中。AQS 通过一个先进先出的(FIFO)等待队列来管理线程之间的同步,能够简化自定义同步器的开发。
AQS 的核心思想是通过一个整型状态变量(state)表示共享资源的状态,来控制线程的获取与释放。
当state>0时表示已经获取了锁,
当state = 0时表示释放了锁,在实现重入锁时,
state > 1 通常表示当前线程已经多次获取了同一个锁,即锁的重入次数。
利用一个 FIFO 的双向链表来管理被阻塞的线程,当一个线程试图获取锁时,如果锁被占用,则该线程会被加入到等待队列中,当锁被释放时,会按照先进先出的顺序唤醒队列中的下一个线程,以重新尝试获取锁。
AQS 支持独占模式与共享模式
独占模式(exclusive):一个线程独占资源,其他线程必须等待。例如 ReentrantLock 就是这种模式。
共享模式(shared):多个线程可以同时访问资源,同一时刻可以有多个线程获取同步状态。例如 CountDownLatch 使用共享模式。
如图所示:
二.公平锁和非公平锁实现
这里只分析ReentrantLock中公平锁和非公平锁的源码实现,其余暂不做拓展了。
瞅瞅源码:
public class ReentrantLock implements Lock, java.io.Serializable {
// ReentrantLock的构造方法,传入布尔值可以构造公平锁对象 还是 非公平锁对象
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
// ReentrantLock的加锁方法
public void lock() {
sync.lock();
}
// 成员变量Sync锁对象
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
/**
* 非公平锁的尝试去获取锁方法
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 拿到同步状态,如果资源刚好是空闲的情况下:不管队列里面有没有线程在排队,直接来一次CAS抢锁操作,抢到就执行
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果占有锁的线程就是当前线程(及锁重入逻辑)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 记录锁的重入次数
setState(nextc);
return true;
}
return false;
}
}
/**
* 非公平锁,继承这个抽象静态内部类Sync
*/
static final class NonfairSync extends Sync {
final void lock() {
// cas加一次锁
if (compareAndSetState(0, 1))
//设置当前为独占访问的线程
setExclusiveOwnerThread(Thread.currentThread());
else
// cas没抢到锁则执行acquire方法,这个方法来自Sync->AbstractQueuedSynchronizer的acquire()方法
acquire(1);
}
// 非公平锁子类实现尝试去获取锁方法--重写父类的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* 公平锁,同样继承这个抽象静态内部类Sync
*/
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
/**
* 公平锁子类实现尝试去获取锁方法--重写父类的tryAcquire方法
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果共享资源状态是空闲
if (c == 0) {
// hasQueuedPredecessors方法来自Sync->AbstractQueuedSynchronizer的hasQueuedPredecessors()方法,如果头节点下一个节点就是当前线程,即没有前驱节点的情况下,进行一次CAS抢锁操作
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
AQS里面的相关方法:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer{
/**
* Node 类主要用于管理线程的排队等待信息,AQS 中的等待队列是一个双向链表,这些节点串联成队列,表示线程在获取锁或者其他同步资源时的排队情况。
* Node 保存了线程的状态、排队信息以及对前驱和后继节点的引用。
*/
static final class Node {
// 共享模式的标志节点:SHARED, 用于标识当前节点是以 共享模式 进行同步操作(例如:CountDownLatch、Semaphore)。
static final Node SHARED = new Node();
// 独占模式的标志节点:EXCLUSIVE,用于标识当前节点是以 独占模式 进行同步操作(例如:ReentrantLock)。
static final Node EXCLUSIVE = null;
// 线程被取消状态:线程在等待过程中被中断或超时,取消状态的节点不会再参与队列调度。
static final int CANCELLED = 1;
// 后继线程需要被唤醒状态: 当前节点释放锁或者资源后,应该唤醒它的后继节点。
static final int SIGNAL = -1;
// 线程正在等待条件队列:表示当前节点正在等待某个条件(用于 Condition 的实现)。当其他线程调用 signal() 时,处于该状态的节点会从条件队列转移到同步队列中。
static final int CONDITION = -2;
// 表示下一次获取共享资源时,可以进行传播唤醒其他节点。这通常在 共享模式 下使用。
static final int PROPAGATE = -3;
// 当前节点的等待状态
volatile int waitStatus;
// 前驱节点
volatile Node prev;
// 后继节点
volatile Node next;
// 当前节点的线程,也就是当前等待获取锁或者资源的线程。
volatile Thread thread;
//这个成员变量用于 Condition 队列中,用来保存条件等待队列中的下一个节点。与同步队列不同,Condition 是一个单向队列。
Node nextWaiter;
}
//......
/**
* 注意这是个短路与, !tryAcquire(arg):尝试去获取锁成功了,则不走后面的添加到等待队列的逻辑。
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
}
// AQS里面的tryAcquire是空方法,待子类去重写的,及会调用到
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
/**
* 判断当前线程是否有前驱线程,也就是判断当前线程是否应该排队等待
*/
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 只要头节点的下一个节点不是当前线程所在的节点,返回 true,表示有前驱节点
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
从上面源码分别对公平锁、非公平锁分析。
- 公平锁:ReentrantLock fairLock = new ReentrantLock(true);
- fairLock.lock();
- lock()方法:只调用acquire(),其余不做任何事
- 公平锁的tryAcquire方法里面:如果共享资源状态是空闲(state==0)情况下,则看有没有前驱线程节点在排队,如果没有在排队的?CAS抢锁 :尾部排队(加入到双向链表尾部)
- 非公平锁:ReentrantLock noFairLock = new ReentrantLock(false);
- noFairLock.lock();
1.lock()方法:上来就cas加一次锁,抢到则占有。
2.在第一次没抢到,则调用非公平锁的tryAcquire方法:拿到同步状态,如果资源刚好是空闲(state==0)的情况下,不管队列里面有没有线程在排队,直接来一次CAS抢锁操作,抢到就执行。
3.非公平锁经过前面两次如果都没抢到锁,那么也要尾部排队了(加入到双向链表尾部)
三.公平锁和非公平锁的区别
- 非公平锁在lock方法会CAS抢一次锁,公平锁不会;
- 非公平锁在tryAcquire方法里面,如果资源刚好空闲则不管队列有没有前驱节点在排队,直接抢锁;而公平锁在tryAcquire方法里面,如果资源刚好空闲则看有没有前驱节点在排队,有则排到队尾,没有则进行CAS抢锁操作;
四.小结
之前有认真学过一遍AQS这块源码,很久没看有点生疏了,写篇博客回顾一下~