AQS(AbstractQueuedSynchronizer):抽象式队列同步器
AQS 是一个FIFO的队列,由双向链表实现,具有内部类Node。它定义了多线程访问共享资源的同步器框架,许多同步类实现都依赖于它。
这里通过ReentrantLock来介绍AQS的底层源码时如何实现的,以下为一个简单的例子:
class Counter{
private final ReentrantLock lock = new ReentrantLock(true);
private int count;
public void add(int n) {
lock.lock();
try {
count += n;
System.out.println(count);
} finally {
lock.unlock();
}
}
}
AQS内部重要成员介绍
在走ReentrantLock源码之前,先看一下AQS内部的成员变量和成员方法,方便之后的解读
//内部类Node
static final class Node {
static final Node SHARED = new Node(); //共享,多个线程可以同时执行,如Semaphore、CountDownLatch、ReadWriteLock,CyclicBarrier
static final Node EXCLUSIVE = null; //独占,只有一个线程能执行,如ReentrantLock
static final int CANCELLED = 1;
static final int SIGNAL = -1; //休眠状态,等待被唤醒
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus; //等待状态,有1,0(初始化状态),-1,-2,-3五个值
volatile Node prev; //指向前节点
volatile Node next; //指向后节点
volatile Thread thread; //用于指向等待锁的线程对象
Node nextWaiter;
final Node predecessor() throws NullPointerException { //返回前节点
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {
}
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
**//AQS通过head,tail,内部类-Node实现一个FIFO的队列(也是一个双向的链表)**
private transient volatile Node head; //head指向队列的头部Node
private transient volatile Node tail; //tail指向队列的尾部Node
private volatile int state; //共享资源--锁状态,加锁成功则为1,重入+1,释放锁为0
protected final int getState() {return state;}
protected final void setState(int newState) {state = newState;}
public final boolean hasQueuedPredecessors() { //判断队列中是否有节点
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
... //其余方法等ReentrantLock源码中分析到的时候再说
ReentrantLock内部结构
ReentrantLock源码中提供了两个构造器方法,分别用来构造公平锁和非公平锁,并用父类变量sync接收。
private final Sync sync; //ReentrantLock的一个字段,可以指向Sync对象或子类(NonfairSync/FairSync)对象
非公平锁:
public ReentrantLock() {
sync = new NonfairSync();
}
公平锁:
public ReentrantLock(boolean fair) {
//fair传参为true,则构建公平锁;否则为非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁–加锁
以公平锁的加锁为例:
//ReentrantLock的lock方法
public void lock() {
sync.lock();
}
//跳到Sync抽象类的抽象方法
abstract void lock();
//Ctrl+Alt+B一下,发现调用的是FairSync中的lock方法
final void lock() {
acquire(1);
}
//调用AQS的acquire方法
//到这一步,tryAcquire,addWaiter,acquireQueued三个方法极为重要,我们分开来解读
public final void acquire(int arg) { //arg=1
//tryAcquire方法:
//1. 返回false,表示未获取到锁,取反后为true,之后执行addWaiter方法,该方法用来把为获取到的线程放入等待队列中
//2. 返回true,表示获取锁成功,取反后为false,则跳出判断,执行完毕,最后跳出lock()方法,继续执行lock之后的代码
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire方法
FairSync的tryAcquire方法:顾名思义,该方法用来尝试获取锁
protected final boolean tryAcquire(int acquires) { //acquires=1
final Thread current = Thread.currentThread(); //获取到当前线程
int c = getState(); //AQS的方法:获取state
if (c == 0) { //如果锁状态为0,即锁未被获取 注:第一个线程尝试获取锁的时候,c必然等于0
//!hasQueuedPredecessors()--判断当前线程之前是否有其他线程排队,即是否有资格去获取锁(因为这是公平锁,先来的先获取)
//compareAndSetState--尝试获取锁,即尝试把state的状态从0改为1。如果当前线程去获取锁的同时,也有其他线程去获取锁。
//1. 如果获取成功,则执行setExclusiveOwnerThread方法,表示当前线程获取了该锁
//2. 如果获取失败,则return false
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果state!=0,判断是是否是持有锁的线程在重入锁
else if (current == getExclusiveOwnerThread()) { //如果当前线程为持有锁的线程,则进行重入锁操作
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc); //将AQS中的state修改,加1
return true;
}
return false;
}
hasQueuedPredecessors----判断队列是否有等待的节点----自认为是这段代码是精髓之一
public final boolean hasQueuedPredecessors() {
Node t = tail; //尾节点
Node h = head; //头节点
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
h!=t:
1. 如果h等于t,说明此时队列未初始化(head和tail都为null)或者队列中没有等待的节点(即只有哨兵节点)
2. 如果h不等于t,说明队列中有等待的线程节点,head指向哨兵节点,tail指向等待的节点。故此h!=t返回true
2.1 继续先执行(s = h.next) == null:
结论:
1. 当队列未初始化或者队列中无等待节点,返回false,表示没有比当前线程更早排队等待获取锁的线程,即当前线程不需要排队
AbstractOwnableSynchronizer的setExclusiveOwnerThread
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread; //表示获取独占锁的线程是哪个线程对象
}

注:以上成功获取锁的流程是比较凑巧的,如果当锁未被占用,某一个线程获取锁的时候,还未初始化队列,或者队列中没有等待的线程,且去获取锁的时候正好被这个线程给获得了。
但是要注意,在并发中,多个线程是会同时执行这个流程的,要站在一个动态的角度去解读并发编程。
所以可能存在以下情况,使得获取不到锁:
1. 锁已经被占用了(c!=0)
2. hasQueuedPredecessors返回false,说明此时队列未初始化或这队列中无等待的线程,之后进行compareAndSetState失败,即被其他线程抢先一步获取到了锁
3. hasQueuedPredecessors的时候返回true,说明有一个线程抢到了锁,并且至少有一个线程入队了。对于公平锁,后来的线程没有资格获取锁
以上的情况,使得tryAcquire返回false,取反后为true,会执行addWaiter方法,就是把当前线程放到队列中去
addWaiter方法–入队自旋
private Node addWaiter(Node mode) { //mode--Node.EXCLUSIVE=null
//这里初始化了一个Node对象,回到AQS内部重要成员介绍中可以知道,这里在Node对象内部用一个变量指向了当前线程对象
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail; //尾节点
if (pred != null) { //这个判断表示是否已经初始化过队列
node.prev = pred; //将当前节点的prev指向原先的尾节点
if (compareAndSetTail(pred, node)) { //将tail指向当前节点
pred.next = node; //原先尾节点的next指向当前节点
return node;
}
}
enq(node); //执行enq的情况:1. 初始化队列 2.compareAndSetTail未成功
return node;
}
AQS的enq方法
1. 如果未初始化过队列则会执行enq方法,进行初始化队列操作
2. 如果addWaiter中的compareAndSetTail未成功,也会执行enq方法
private Node enq(final Node node) {
for (;;) { //死循环
Node t = tail;
if (t == null) { //执行这里的代码说明当前的队列还未初始化
//compareAndSetHead:
//1. new Node():创建一个thread变量为null的节点,并将head指向该节点
//2. tail也指向该节点
//说明:队列不是在构造的时候初始化的, 而是延迟到需要用的时候再初始化, 以提升性能
if (compareAndSetHead(new Node()))
tail = head; //同时将tail指向该节点
} else { //这里用于设置尾节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t; //最终都在这里跳出死循环
}
}
}
}
注:
1. enq方法中的compareAndSetHead操作可能失败:当多个线程同时在还未初始化队列的时候执行该CAS操作,都想要将head和tail指向这个thread为null的节点。
2. 但是在这里失败的线程,或者是成功的线程都将进入下一个循环。
3. 此时,队列已经被初始化了,每个线程都要执行compareAndSetTail方法,将自己这个线程节点放到尾节点。
4. compareAndSetTail失败的节点会继续进入下一个循环,执行compareAndSetTail,直到在某个时刻执行CAS成功,在(return t)处跳出死循环。

多个线程节点同时入队时,会出现以下情况:

addWaiter总结:
acquireQueued----线程在这里阻塞;自旋
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); //获取当前节点的前节点
//如果前节点为哨兵节点(即当前节点是第一个等待锁的线程;如果当前节点不是第一个等待锁的线程,没有资格去尝试获取锁)
//且获取锁成功(可能当前线程入队后,锁正好被释放了,所以在入队后自旋tryAcquire,尝试获取锁)
if (p == head && tryAcquire(arg)) {
setHead(node); //这里将head指向了获取到锁的node节点,这个节点的thread被置为null,成为哨兵节点
p.next = null; //原先的哨兵节点出队列
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHead(Node node) { //将节点设置为头节点(哨兵节点)
head = node;
node.thread = null;
node.prev = null;
}
shouldParkAfterFailedAcquire–获取锁失败后判断是否应该park
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { //pred--前节点 node--当前节点
int ws = pred.waitStatus; //前节点的等待状态
if (ws == Node.SIGNAL) //如果前节点的waitStatus已经是-1,即等待唤醒的状态,则return true,然后执行parkAndCheckInterrupt睡眠
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); //将前节点的ws赋值为-1,表示等待唤醒状态
}
return false;
}
parkAndCheckInterrupt----在这里睡眠
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
重回hasQueuedPredecessors

本文深入解析AQS(AbstractQueuedSynchronizer)的内部机制与ReentrantLock的源码实现,阐述了AQS如何通过双向链表实现FIFO队列,以及ReentrantLock如何基于AQS实现公平锁与非公平锁。
460

被折叠的 条评论
为什么被折叠?



