AQS简单介绍
AbstractQueuedSynchronizer结构简单介绍
通过其内部结构大致可以了解到
1、AQS其实是一个双向链表,有head、tail节点分别代表首尾节点;每个节点的元素的类型是Node;
2、在AQS中维护了一个int类型的state属性,这个属性在不同的子类中存在不同的含义;例如在Semaphore中表示的就是信号量;在ReentrantLock中表示的是当前线程获取锁的可重入次数;
3、在Node节点中的thread表示的是进入到AQS中的线程;其中SHARED表示的是该线程是在获取共享资源被阻塞后台放入到阻塞队列中的;EXCLUSIVE表示是在获取独占资源被阻塞后放入到阻塞队列中的;在Node中的waitStatus表示的是对应线程的状态,其中CANCELLED(1:线程已经被取消)、SIGNAL(-1:表示线程需要被唤醒)、CONDITION(-2:线程在条件队列里面等待)、PROPAGATE(-3:释放共享资源时需要通知其他线程)表示对应的
大致了解之后,来看一下AQS中几个相当重要的方法;
- void acquire(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1、该方法获取独占资源,先调用tryAcquire方法尝试获取独占资源;该方法的实现有子类定义;
2、在独占的资源没有获取到的时候,向队列中添加一个EXCLUSIVE类型的的Node节点到AQS阻塞队列的尾部;
3、然后调用LockSupport.park(this)挂起;
接下来一个简单了解一下每个方法的内部执行逻辑
- boolean addWaiter(Node mode)
private Node addWaiter(Node mode) {
//(1)
Node node = new Node(Thread.currentThread(), mode);
//(2)
Node pred = tail;
//(3)
if (pred != null) {
//(3.1)
node.prev = pred;
//(3.2)
if (compareAndSetTail(pred, node)) {
//(3.2.1)
pred.next = node;
//(3.2.2)
return node;
}
}
//(4)
enq(node);
return node;
}
(1)为当前线程构建一个新的node节点;
(2)获取AQS队列中的尾节点;
(3)判断尾节点是否不为null;
(3.1)将新的节点的前置节点设置为尾节点;
(3.2)通过CAS将当前的尾节点设置为新的节点;这里使用CAS的原因是为了防止其他的线程将AQS队列中的tail元素修改成了别的Node元素;
(3.2.1)CAS操作成功之后将将尾节点的后置节点设置为新的节点;操作失败进入(4)
(3.2.2)返回新节点
(4)当前AQS队列中的tail节点为null,那么这个时候可以认为AQS队列为null,然后通过enq(fianl Node node)方法进行元素新增;
接下来简单分析一下enq(fianl Node node)方法;
- Node enq(final Node node)
private Node enq(final Node node) {
for (;;) {
//(1)
Node t = tail;
//(2)
if (t == null) { // Must initialize
//(2.1)
if (compareAndSetHead(new Node()))
//(2.2)
tail = head;
//(3)
} else {
//(3.1)
node.prev = t;
//(3.2)
if (compareAndSetTail(t, node)) {
//(3.3)
t.next = node;
return t;
}
}
}
}
此方法根据boolean addWaiter(Node mode)方法的执行情况进行分析;
1、当AQS中的tail节点为null时,进入此方法,也就是第一次循环
(1)这里重新做一次取值,是为了避免在此过程中,tail被其他线程修改了;这里默认没有被修改,那么在(2)中t== null成立;
(2.1)中将head节点设置为哨兵节点(new Node());
(2.2)head节点设置成功之后,将head节点赋值tail节点;这个熟tail节点就不为空了;
2、(2.2)中将head节点的值赋值给了tail节点之后,第一次循环结束,进行第二次循环,这次循环很明显tail != null了
然后进入(3)
(3.1)将新增的节点的前置节点只想哨兵节点
(3.2)设置尾节点tail尾新增的节点node;
(3.3)将哨兵节点的后置节点指向新增的节点node,然后返回
执行相关的大致流程图:
然后再看acquireQueued(final Node node, int arg)方法
- boolean acquireQueued(final Node node, int arg)
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//(1)
final Node p = node.predecessor();
//(2)
if (p == head && tryAcquire(arg)) {
//(2.1)
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
接着addWaiter方法来分析一下acquireQueued方法;
1、 获取当前节点的prev节点
2、 如果prev节点为head节点,那么它就有资格去争抢锁,调用tryAcquire抢占锁
3、 抢占锁成功以后,把获得锁的节点设置为head,并且移除原来的初始化head节点
4、如果获得锁失败,则根据waitStatus决定是否需要挂起线程
5、 最后,通过cancelAcquire取消获得锁的操作
2、boolean release(int arg)
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
1、该方法会先调用boolean tryRelease(int arg)方法尝试释放资源(具体实现由子类定义)
2、在释放资源成功后,判断AQS队列中是否存在被阻塞的线程
3、存在被阻塞的线程则调用void unparkSuccessor(Node node)进行唤醒;