文章目录
-
- [1 概要](#1 概要)
- [2 技术名词解释](#2 技术名词解释)
- [3 AQS核心方法原理](#3 AQS核心方法原理)
-
- [3.1 acquire(int arg)](#3.1 acquire(int arg))
- [3.2 release(int arg)](#3.2 release(int arg))
- [3.3 acquireInterruptibly(int arg)](#3.3 acquireInterruptibly(int arg))
- [3.3 acquireShared(int arg)](#3.3 acquireShared(int arg))
- [3.4 doReleaseShared()](#3.4 doReleaseShared())
- [3.5 releaseShared(int arg)](#3.5 releaseShared(int arg))
- [3.6 acquireSharedInterruptibly](#3.6 acquireSharedInterruptibly)
- [3.7 hasQueuedPredecessors()](#3.7 hasQueuedPredecessors())
- [4 总结](#4 总结)
1 概要
AQS在类的注释上说的已经很明白,提供一个框架,用于实现依赖先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量、事件等)。此类被设计做为大多数类型的同步器的一个有用的基础类,这些同步器依赖于单个原子int值(state字段)来表示状态。
2 技术名词解释
CAS:cas是比较并交换(compare and swap)的缩写,java对其具体实现是Usafe类,它有一系列的compareAndSwap方法
3 AQS核心方法原理
3.1 acquire(int arg)
从代码逻辑上可以看到,首先尝试获取,如果此时返回true, 执行结束。相当于获取到锁了。tryAcquire方法是抽象方法,比如公平锁和非公平锁的不同实现逻辑
如果尝试获取失败,会进入acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。
java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//没获取到锁但是park住了,如果unpack且如果在等待的过程中发生中断走到这,进行中断位标记
selfInterrupt();
}
此时具体看下acquireQueued(addWaiter(Node.EXCLUSIVE), arg)逻辑。
- addWaiter(Node.EXCLUSIVE)
java
private Node addWaiter(Node mode) {
//创建Node
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
//先尝试一次原子加入链表尾部中
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//走下述逻辑,上面尝试未成功,或者此时tail == null(因为初始化的时候都是null 在后续才会设置值)
enq(node);
return node;
}
private Node enq(final Node node) {
//死循环+原子性加到列表中
for (;;) {
Node t = tail;
if (t == null) { // 初始化头结点和尾结点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- acquireQueued(final Node node, int arg)
因为addWaiter(Node mode)最终是死循环+原子性加到列表中,是肯定成功的。也就意味着需要阻塞当前线程了。但是有没有挽救的余地呢?
todo1
java
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取当前线程节点的前一个节点,因为在上步已经添加到tail中了
final Node p = node.predecessor();
//如果此时前一个节点时头节点,因为在概要中已经说明,先进先出队列,所以此时前面就一个头
//结点,此时你就是阻塞队列第一个,那没线程阻塞啊,所以此时再尝试下获取锁
if (p == head && tryAcquire(arg)) {
//此时获取到锁,那么线程安全的情况下设置头结点的node
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//尝试还是没获取到锁,那么原子设置pred.waitStatus = -1。waitStatus 状态下面会描述
if (shouldParkAfterFailedAcquire(p, node) &&
//设置pred.waitStatus = -1成功了 会park阻塞线程,并返回该过程中是否被中断过
parkAndCheckInterrupt())
//这地方是unpark 或者中断之后的逻辑,又会回到上面去获取锁,如果pre不是头节点,又会被park住。todo1 做个标记
interrupted = true;
}
} finally {
if (failed)//正常情况下不会到这
cancelAcquire(node);
}
}
总结acquire(int arg):在多线程的情况下,有一个线程tryAcquire获取到锁,其余的cas进入等待队列,当然头节点的下一个或尝试下,没有获取成功的都会被park当前线程。进入阻塞状态等待被唤醒
3.2 release(int arg)
先看整体逻辑,和获取锁是一致且相反的逻辑。先尝试释放锁,释放失败直接false。还可以释放失败?? 如果锁是可重入的,那么需要把state缩减成0才算释放。
tryRelease(arg)是个接口方法,子类实现。
java
public final boolean release(int arg) {
if (tryRelease(arg)) {
//如果成功了,
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
//waitStatus下面描述
int ws = node.waitStatus;
if (ws < 0)
//把当前节点的waitStatus修改成0,如果失败了呢,好像也不影响,
compareAndSetWaitStatus(node, ws, 0);
//s.waitStatus > 0就是线程取消状态,也即无效的节点,再次剔除掉找到有效的节点,从尾往头找
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//正常情况下从头取等待的线程唤醒,但是如果头节点的next节点是被取消了,那就从尾部找了,好像也不是严格的先进先出啊....
if (s != null)
LockSupport.unpark(s.thread);
}
3.3 acquireInterruptibly(int arg)
相对于3.1的acquire(int arg),该方法会对线程获取锁的过程中线程发生中断抛出异常
java
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//先尝试获取锁,未获取到进度会抛异常的获取锁方法
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
//对比下
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//区别就在这,当parkAndCheckInterrupt()返回true时,也就是获取锁结果被阻塞住了,期间发生线程中断会抛出打断异常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.3 acquireShared(int arg)
获取共享锁。同比上述排它锁,相同逻辑先尝试获取共享锁tryAcquireShared(arg),然后进入doAcquireShared
java
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
//和上述一样添加阻塞队列,只不过是共享标识
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
//和上述一样排它锁一样
int r = tryAcquireShared(arg);
if (r >= 0) {
//区别在这 独占锁setHead(node);只是设置了头节点,但是共享锁在获取到锁之后,不仅要设置头节点,
//此时还需要判断后续节点。因为是共享的,你拿到锁之后,后续链表中挨着的共享节点也可以被unpark
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
//和独占锁一样,不带Interruptibly的方法只是帮你设置下中断位
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//和独占锁一样,不带Interruptibly的方法只是帮你设置下中断位
//此时这唤醒之后 是不是又到循环上面去了,这个节点就是头结点的next节点
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
//此时node就是头节点 设置进去
setHead(node);
//这是共享锁在获取到锁之后的多余的操作,就是顺着链表传播下去。为什么呢?举个场景,共享读锁 如果此时下一个节点还是共享锁,其实是需要唤醒的,此情况下是链表中的顺序 独占,共享,共享,共享这种情况
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
//
if (s == null || s.isShared())
//如果下一个节点是共享的就释放锁
doReleaseShared();
}
}
}
3.4 doReleaseShared()
java
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
//可能有其他线程调用doReleaseShared(),unpark操作只需要其中一个调用就行了
//正常情况下就是修改成0,unpark改线程,unparkSuccessor上面已描述
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
//如果上述失败,修改成-3 为了同时release的情况,此时设想下,如果不改成-3,只变成0 release就下不来了,就会一直hang住
//为啥是-3呢 我猜是符合<0的条件吧
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
3.5 releaseShared(int arg)
上面已描述
java
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
3.6 acquireSharedInterruptibly
按照上面的思路很明白的知道会主动抛出中断异常的获取锁的方法
3.7 hasQueuedPredecessors()
java
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
//就是判断当前阻塞队列里是否有阻塞的线程node
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
总结acquireShared:可以资源共享,共享锁,相对于上述独占锁,在创建Node的时候会标记成SHARED,释放资源唤醒头结点的next节点的时候会唤醒紧挨着的SHARED Node节点吗,当然是一个唤醒另一个,因为在if (p == head)的那里循环产生的。
4 总结
如类名一样,通过Queue的Node链表来保存阻塞的线程信息,通过state字段的原子操作代表获取到的资源情况,这个字段是给子类实现用的。
关于AQS的ConditionObject 详解请看详解二
java并发编程 AbstractQueuedSynchronizer(AQS)详解二