文章目录
- 一.什么是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) // overflowthrow 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这块源码,很久没看有点生疏了,写篇博客回顾一下~