1.是用来构建锁或者其他同步器组件的重量级框架,是整个 JUC 底层实现,内置 FIFO队列完成资源获取和排队工作,并通过一个 int 变量来表示持有锁状态。
2.是提出锁的一种规范,屏蔽同步状态的管理,阻塞线程排队和通知,唤醒机制。
3.加锁就会阻塞线程,有阻塞就会产生排队,有排队就需要队列,既然说到排队,那么就一定需要有某种队列形成,这样的队列是什么数据结构呢?
如果共享资源 state 被占用,就需要一定的阻塞唤醒机制来保证锁分配,这个机制主要用的是 CLH 队列变体实现的,将暂时获取不到锁的线程加入到 CLH队列 中,这个队列就是 AQS 的抽象表现,它将请求共享资源的线程封装到了队列的 Node 节点中,通过 CAS、自旋以及 LockSupport.park() 方式,维护 AQS 中的变量 state 状态,使并发达到同步的效果。如下图示:

CLH(Craig,Landin and Hagersten)是三个人,共同发明了一个可扩展、高性能、公平且基于自旋锁的链表
上述图中展示了 AQS 的框架结构,下面在继续查看该类内部相关信息。
AbstractQueuedSynchronizer 是一个抽象类(既然是抽象类,肯定提供了模版方法要让我们自己实现的啦),先来看看有哪些成员(摘出的是 AQS 主流程需要用到的成员信息),如下所示:
public abstract class AbstractQueuedSynchronizer {
// AQS 同步器中指向头结点的引用
private transient volatile Node head;
// AQS 同步器中指向尾节点的引用
private transient volatile Node tail;
// 线程共享资源
private volatile int state;
// Node 内部类封装Thread和线程状态,以及锁是独占锁还是共享锁
static final class Node {
// 共享锁
static final Node SHARED = new Node();
// 独占锁(默认)
static final Node EXCLUSIVE = null;
// 线程结束运行了
static final int CANCELLED = 1;
// 表示线程已经被阻塞了需要等待被唤醒
static final int SIGNAL = -1;
// 共享锁涉及
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// 线程的状态,初始值是 0
volatile int waitStatus;
// Node 的前序节点
volatile Node prev;
// Node 的后驱结点
volatile Node next;
// 当前线程
volatile Thread thread;
}
}
AQS 类组成结构:
head、tail、state、内部类Node),AQS 类设计理念是队列,遵循 FIFO 原则,所以自然而然肯定有 head ,tail 两个头尾节点,然后共享资源 state ,还有个 Node 内部类组成 AQS 抽象类。AbstractOwnableSynchronizer 类中还有一个属性 exclusiveOwnerThread,用来存储加锁成功的线程引用。Node 内部类组成结构:
Node 里封装的是 Thread 线程waitStatus 表示线程的状态(默认初始值是0、还有 CANCELLED(1)、SIGNAL(-1)、 CONDITION(-2)、 - PROPAGATE(-3) 总共五种状态)prev 前驱节点next 后驱节点mode 表示是要加独占锁还是共享锁(默认独占锁 EXCLUSIVE)由此可以看出 AQS 是由上述两大部分组成,里面的内部类 Node 其实是一个双端链表。
因为 AQS 是个抽象类,必不可少的肯定要儿子类去帮他完成一些事情,所以除了上面的属性之外还提供了4个常用 API 如下:
tryAcquire(int arg) // 尝试加独占锁tryAcquireShared(int arg) // 尝试加共享锁tryRelease(int arg) // 尝试释放独占锁tryReleaseShared(int arg) // 尝试释放共享锁总结起来:同步器中有一个 state 变量控制锁的装态,同时还要知道哪个线程作为 head,tail节点,外层就只有这三个重要结构,内部有个 Node 类封装了线程以及状态,Node 结构还是一个双向链表,pre,next节点的指向前后的 Node 节点。
总结流程图如下:

AQS 本身是一个抽象类,里面的方法都是空方法,所以我们从它的著名子类 ReentrantLock 开始分析,先来看一个最简单熟悉不过的代码,如下:
public class LockSupportDemo {
public static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock();
// ...doSomething
lock.unlock();
}
}
进入 lock 内部方法,如下:
public void lock() {
sync.lock();
}
发现调用的是 sync.lock(),那这个 sync 是啥玩意呢?继续查看 sync 代码如下:
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
}
}
发现 ReentrantLock 底层实际用的是 AQS,自己写了个内部类叫 Sync,继承 AQS,覆写 AQS 中提供的API :
tryAcquire(int arg) // 尝试加独占锁tryAcquireShared(int arg) // 尝试加共享锁tryRelease(int arg) // 尝试释放独占锁tryReleaseShared(int arg) // 尝试释放共享锁继续进入 sync.lock() 内部发现他有两个子类实现,如下:

发现是我们平常所说的公平锁,和非公平锁,我们这里先看非公平锁 NonfairSync,代码如下:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 调用的子类 NonfairSync 里的 lock 方法
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
发现 NonfairSync 又是 Sync 的子类,调用 lock 方法,内部可以看到是使用 CPU 原语指令 CAS 操作。进来就开始尝试 CAS 加锁,加锁成功返回,失败走 acquire(1),因为第一次进来,state 默认值是 0,执行这条指令 compareAndSetState(0, 1) 肯定是成功的,然后加锁成功的线程会把自己的大名贴上,我占用了这把锁,我没有释放之前,其他线程是不能加锁的(独占锁)。
但是如果此时其他线程过来抢夺这把锁,也就是想修改 state 的值,假设线程1没有释放锁,那就只能走 else 操作了,进入 acquire(1) 内部,代码如下:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
}
会发现有三部分构成tryAcquire()、acquireQueued()、addWaiter()。这里开始调用 AQS 提供的模版方法,tryAccquire() 这个方法 AQS 是没有实现的,子类 ReentrantLock 自己实现,后面的 addWaiter() 、acquireQueued() 方法都是 AQS 内置方法,不需要程序员去管理的。先进入到 tryAccquire() 方法内部,如下:
abstract static class Sync extends AbstractQueuedSynchronizer {
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
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;
}
}
线程过来会先通过调用 AQS 提供的 getState() 方法取出内存中的 state 值,判断是否为0 ,0是没有人修改过,非0肯定是有其他线程修改过,很显然,线程1没有释放锁,现在这个线程肯定是不能加锁成功的。else if 中的逻辑是判断可重入锁的逻辑,如果判断当前线程和 AQS 中贴上的线程是一致的,那就给 state 加 1,释放的时候也要减1,直到减到1才算释放成功。显然当前线程肯定不是 AQS 上加锁成功的线程,所以当前线程加锁失败,返回 false,然后再返回上一层逻辑。
继续执行后面的语句 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 这条语句我们先看最里面的 Node.EXCLUSIV 表示要创建一把独占锁,但是此时这个默认值是 null,也就是说现在传入的 Node = null,代码如下:

然后再进入到 addWaiter() 方法内部,如下:
abstract static class Sync extends AbstractQueuedSynchronizer {
private Node addWaiter(Node mode) {
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;
}
}
enq(node);
return node;
}
}
会发现,这里新建了一个 node 节点(暂时把名字叫做 Node1),并把当前线程封装进去了,同时设置为独占锁模式。然后 tail 是 AQS 中的属性,用来指向队列的尾节点,默认值是 null,所以这里 pred = null,直接走 enq(node) 代码,进入方法内部,如下:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
发现是一个自旋 CAS 操作,线程进入到 for 循环,因为第一次进来 tail = null ,所以会新建一个空 Node 节点(暂时把名字叫做 Node2),里面的属性(prev,next,thread,waitStatus(默认值0))全部都还没有赋值,然后执行代码 compareAndSetHead(new Node()) ,表示将 AQS 同步器中的 head 头指针指向新建的空节点(也可称之为哨兵节点),此时 Thread = null、waitStatus=0 ,如下图示:

1.如果没有哨兵节点,那么每次执行入队操作,都要判断
head是否为空,如果为空则head=new Node如果不为空则head.next=new Node, 而有哨兵节点则可以直接建立引用head.next=new Node。
2.如果没有哨兵节点,可能存安全性问题,当只有一个节点的时候执行入队方法,无法保证tail和head不为空。哪怕节点入队前tail和head还指向同一个节点,下一时刻可能由于并发性在具体调用enqueue方法操作tail的时候,head和tail共同指向的头节点已经完成出队,此时tail和head都为null,所以enqueue方法中的tail.next=new node会抛空指针异常,这就是哨兵节点添加进去的可以有效避免的问题。
Node1 是线程过来新建的不为空的节点,里面的封装着 ThreadB,waitStatus=0;Node2 是线程过来新建的空节点,Thread=null, waitStatus=0。然后再执行代码 tail = head,表示将 AQS 中尾指针指向哨兵节点,如下图示:

注意,循环还没有结束呢,然后再走一遍逻辑,此时的 tail 肯定是不为 null 的,已经指向了 Node2 哨兵节点,然后执行代码 node.prev = t;(t是tail,指向了 Node2 哨兵节点,node 就是我们传进来的 Node1) 这句代码表示将Node1 的前驱指针指向 tail,tail 此时指向的是 Node2 哨兵节点,如下图示:

然后执行代码 compareAndSetTail(t, node) 表示把 AQS 的尾指针指向 Node1,队列尾指针肯定是永远指向尾部的。如下图示:

然后再执行代码 t.next = node; (t是tail,原来指向的是 Node2 哨兵节点) 这句代码表示将 Node2 哨兵节点的后驱指针指向 Node1 节点,如下图示:

程序运行到此整个 enq() 方法就算结束了,后面就不会再进来了,返回上一层,代码如下:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
看到线程执行完 enq() 方法之后,就把当前的 node(这里我们上面说了暂时命名为 Node1 节点) 返回出去,我们这里先不返回出去,先来看看这个 if 里面的逻辑,假设其他线程第三次过来执行这个 addWatier () 方法,此时传过来的 node 我们称之为 Node3,此时 tail 肯定不为 null(已经指向了 Node1 节点),所以会进入 if 的逻辑,开始执行 这行代码 node.prev = pred; 表示把 Node3 节点的前驱指针指向 pred,pred是谁呢?是tail,测试 tail 指向的是尾部节点 Node1,上述过程演示过的,尾部节点是 Node1,所以自然而然,这里把 Node3 的前驱指针指向了 Node1,如下图示:

然后再执行代码 compareAndSetTail(pred, node) 把 AQS 的 tail 尾部尾指针向后移动,指向最后一个节点,也就是最后入队列的节点 Node3 ,如下图示:

执行完之后,在执行代码 pred.next = node;(pred 原来的尾指针指向的对象,也就是 Node1 节点) 意思是把 Node1 节点的后驱指针指向 Node3,如图示:

最后返回 Node3 节点,然后这整个 addWaiter() 方法就分析完了,无非就是在两件事情,第一是创建空的哨兵节点,第二将所有的过来的 Thread 入队然后建立好引用,分析完 addWaiter() 然后返回代码上一层,代码如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
继续进入 acquireQueued() 方法内部,代码如下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
第一个线程过来执行代码 node.predecessor() 表示返回节点的前驱节点。
假设1我们现在过来的节点是 Node1 节点,arg 是外面写死的1,那么 Node1 的前驱节点自然而然是 Node2 哨兵节点(由上面最后一张图可以看出)
然后执行在执行 if 里面的判断逻辑,发现 p 指向的是 Node2,head 指向的也是 Node2,所以 p==head 条件成立,表示你这个节点是即将第一个出队列的节点,所以紧接着就会让你重新尝试一次加锁,也就是执行代码tryAcquire(arg),也就是走下面这段代码:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
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;
}
假设1的第1种情况如果过来的线程加锁成功,那么就会进入到 if 里面的逻辑,执行代码 setHead(node); 先看看 setHead() 方法内部,代码如下:
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
表示把 AQS 的 head 头指针指向了 Node1 节点,其实就是相当于把 Node1 节点出队列了,然后 Node1 节点会被当做新哨兵节点,如下图示:

虚线标识是没有指向了,灰色表示没有任何引用了,可以被 GC 垃圾回收,此时 AQS 的头指针指向了 Node1 节点,Node1 节点会被当做新的哨兵节点,那么此时里面的 Thread 也可以置为空,因为 Node1 节点中的 ThreadB 已经加锁成功了,已经出队列了,然后 Node3 节点也是和 Node1 节点一样加锁成功就出队列。
假设1的第二种情况如果加锁失败了,就会执行 shouldParkAfterFailedAcquire(p, node) 方法,进入代码如下:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
Node1 节点过来执行,pred 节点是哨兵节点,此时的 waitStatus=0,没人修改过,会走到最后一个else 执行代码 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 表示将哨兵节点的 waitStatus 状态改成 SIGNAL(-1)状态,其实这个方法就是每次新过来的节点会把前置节点的 waitStatus 修改成Node.SIGNAL(-1)状态, 然后回到外层,是一个 for 循环,还会再次进入在此进行判断一遍,此时哨兵节点中 waitStatus 已经被赋值为 -1,所以会返回 true,回到上一层代码如下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
如下图示:

然后再进入到方法 parkAndCheckInterrupt() 内部,代码如下:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
让当前线程挂起,如果没有中断 Thread.interrupted() 返回 false,并复位也还是 false。
那么这个挂起的线程什么时候被唤醒呢?为什么会被挂起呢?肯定是因为想去尝试加锁,加锁失败,那么只有等锁要被释放的时候,这些阻塞的线程才会被唤醒然后去竞争加锁,所以这里就需要看释放锁的过程了,还是比较简单的。
进入代码 lock.unlock() 内部,代码如下:
public void unlock() {
sync.release(1);
}
注意这里是传入的常量值1哦,再进入 release() 方法内部,代码如下:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
进入到 tryRelease() 方法内部,代码如下:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
会发现释放锁比较简单,也就是将之前的修改的 state 开始复位操作,然后把占用的坑位清空即可。假设释放锁成功了,返回true,开始执行代码 unparkSuccessor(),进入内部代码如下:
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 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;
}
if (s != null)
LockSupport.unpark(s.thread);
}
Node 是传进来的 head 头指针指向的节点,也就是 Node2 哨兵节点,此时 Node2 的 waitStatus 已经在加锁过程中的 shouldParkAfterFailedAcquire() 方法修改成了 Node.SIGNAL 状态(-1),所以此时第一个 if 逻辑是会执行的,执行 compareAndSetWaitStatus(node, ws, 0); 表示将 Node1 哨兵节点的 waitStatus 重新改为默认值 0 ,此时的 Node2 的后驱节点是 Node3,所以 s!=null 条件成立,开始执行 LockSupport.unpark(s.thread); 唤醒操作。
那么唤醒之后,原来阻塞在那边的线程就要开始复活,然后接着从阻塞线程的地方开始往下分析,代码如下;
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
parkAndCheckInterrupt() 方法刚才挂起了线程,现在 Node1 线程被唤醒,那么会立即执行么?不一定的哦,因为可能有新建来的线程竞争插队执行的,假设没有线程插队,Node1 唤醒,然后开始往下执行继续走 for 循环,又开始执行第一个if判断,p==head 成立,尝试加锁也成功,然后把 Node1 作为哨兵节点,并隔离和原来哨兵节点的引用。
假设现在有阻塞的线程被中断了,那么在执行这句代码 parkAndCheckInterrupt() 会返回当前的中断标志位,并且还复位了,代码如下:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
被中断了那 Thread.interrupted() 返回结果 true,但是这个方法会复位(线程自己操作的,不是你干的,你是要中断,他又帮你复位了,所以他需要再别地方在重新帮你中断一次,不然你的中断就无任何意义了),在外层还会自己调用一次中断,重新中断下。代码如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
这里重新帮你执行一次interrupt() 方法再次中断标识位。但是只是设置了中断标志位,如果你自己灭有在某个地方退出线程,此线程还是可以加锁成功的。也就是你设置了跟没设置一个样,毫无影响。
