这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
一、前言
AbstractQueuedSynchronizer 简称 AQS,抽象队列同步器
它是
JUC包实现同步的基础工具。
AQS是 抽象类, 内置自旋锁实现的同步队列, 封装入队和出队的操作, 提供独占、共享、中断等特性的方法。
在 AQS 中, 定义了一个volatile int state变量作为共享资源:
- 如果线程获取资源失败, 则进入同步
FIFO队列中等待 - 如果成功获取资源就执行临界区代码
- 执行完释放资源时, 会通知同步队列中的等待线程来获取资源后出队并执行
简单概括: AQS 利用 CAS 维护状态, 利用 LockSupport来对线程进行操作。
利用 AQS 类的主要步骤:
- 第一步:新建一个自己的线程协作工具类,在内部写一个
Sync类,该Sync类继承AbstractQueuedSynchronizer,即AQS; - 第二步:想好设计的线程协作工具类的协作逻辑,在
Sync类里,根据是否是独占,来重写对应的方法。如果是独占,则重写tryAcquire和tryRelease等方法;如果是非独占,则重写tryAcquireShared和tryReleaseShared等方法; - 第三步:在自己的线程协作工具类中,实现获取/释放的相关方法,并在里面调用
AQS对应的方法,如果是独占则调用acquire或release等方法,非独占则调用acquireShared或releaseShared或acquireSharedInterruptibly等方法。
AQS 实现原理
AQS 里最重要的两个东西:
state:表示状态,0 表示未加锁、1 表示已加锁- 队列:挂起的线程存放的位置
AQS初始,如图:
- 线程1 和 线程2 并发执行,加锁,如图:
- 线程1 执行完成,如图:
- 线程3 加入,公平锁与非公平锁:
二、问题
(1)为什么需要 AQS
- 相对与
synchronized基于JVM,AQS更加轻量化, 更易掌控 AQS底层依靠CAS, 相对于挂起线程(切换上下文), 有时这个相对快速, 性能较好
(2)如何加锁
其实看上面的原理,就会明白,加锁主要两步:
-
判断
state,更新state = 1 -
设置
AQS的线程为当前线程其他线程,则会进入队列,并挂起
(3)如何释放锁
释放锁,理所当然是加锁的逆过程嘛:
- 更新
state - 更新
AQS的线程为null - 唤醒队列中队头的线程
如何唤醒队头的元素?
使用
LockSupport.unpark(thread)方法。
队头的元素唤醒之后是如何重新尝试加锁的呢?
- 先从阻塞状态中恢复
- 之后继续走
for(;;)循环,从头开始判断
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 1. 线程被阻塞在这
// 2. 当被 LockSupport.unpark(thread) 之后
// 会从这边继续运行
return Thread.interrupted();
}
(4)state 可以大于 1嘛?
可以的。
重入锁,state的值就会增加。
当然,释放的时候,也需要减少。
三、设计思想
主要动作:
CAS维护stateLockSupport操作线程
同时, 采用了模板模式
子类可以通过实现 protected 方法, 自定义实现自己的操作逻辑, 从而对 state 进行操作。
下面列举一些常见需要子类实现的方法:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
四、源码解析
主要从四个方面: Node、独占锁、共享锁、 LockSupport,
共享锁 与 独占锁的区别: 是否被多个线程
(0) Node 队列节点
每一个
Node都持有一个线程
static final class Node{
/*当前node对象的等待状态,注意该状态并不是描述当前对象而是描述下一个节点的状态,
* 从而来决定是否唤醒下一个节点,该节点总共有四个取值:
* a. CANCELLED = 1:因为超时或者中断,结点会被设置为取消状态,被取消状态的结点不应该去竞争锁,
* 只能保持取消状态不变,不能转换为其他状态。处于这种状态的结点会被踢出队列,被GC回收;
* b. SIGNAL = -1:表示这个结点的继任结点被阻塞了,到时需要通知它;
* c. CONDITION = -2:表示这个结点在条件队列中,因为等待某个条件而被阻塞;
* d. PROPAGATE = -3:使用在共享模式头结点有可能牌处于这种状态,表示锁的下一次获取可以无条件传播;
* e. 0: None of the above,新结点会处于这种状态。
*
* 非负值标识节点不需要被通知(唤醒)。
*/
volatile int waitStatus;
//当前节点的上一个节点,如果是头节点那么值为null
volatile Node prev;
//当前节点的下一个节点
volatile Node next;
//与Node绑定的线程对象
volatile Thread thread;
//下一个等待条件(Condition)的节点,由于Condition是独占模式,因此这里有一个简单的队列来描述Condition上的线程节点。
Node nextWaiter;
}
(1) 独占锁
流程,如图:
acquire()获取锁
public final void acquire(int arg) {
// 1. 尝试获取同步状态, 成功就退出
// 2. 若失败, 则添加至队尾
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
private Node addWaiter(Node mode) {
// 1. 将当前线程构建成Node类型
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;
}
}
// 2. 当前同步队列尾节点为null,说明当前线程是第一个加入同步队列进行等待的线程
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//1. 构造头结点
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 2. 尾插入,CAS操作失败自旋尝试
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 自旋的过程
for (;;) {
// 1. 获得当前节点的先驱节点
final Node p = node.predecessor();
// 2. 当前节点能否获取独占式锁
// 2.1 如果当前节点的先驱节点是头结点并且成功获取同步状态,即可以获得独占式锁
if (p == head && tryAcquire(arg)) {
//队列头指针用指向当前节点
setHead(node);
//释放前驱节点
p.next = null; // help GC
failed = false;
return interrupted;
}
// 2.2 获取锁失败,线程进入等待状态等待获取独占式锁
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(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;
}
private final boolean parkAndCheckInterrupt() {
//使得该线程阻塞
LockSupport.park(this);
return Thread.interrupted(); // 返回是否阻塞
}
release()释放锁
public final boolean release(int arg) {
// tryRelease() 是各实现类需要实现的方法
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 每一次锁释放后就会唤醒队列中该节点的后继节点所引用的线程,从而进一步可以佐证获得锁的过程是一个FIFO(先进先出)的过程。
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)
//后继节点不为null时唤醒该线程
LockSupport.unpark(s.thread);
}
acquireInterruptibly可中断式获取锁
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())
//线程中断抛异常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
tryAcquireNanos()超时等待式获取锁
即, 调用
lock.tryLock(timeout, TimeUnit)方式
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
//实现超时等待的效果
doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
//1. 根据超时时间和当前时间计算出截止时间
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
//2. 当前线程获得锁出队列
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
// 3.1 重新计算超时时间
nanosTimeout = deadline - System.nanoTime();
// 3.2 已经超时返回false
if (nanosTimeout <= 0L)
return false;
// 3.3 线程阻塞等待
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
// 3.4 线程被中断抛出被中断异常
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
(2) 共享锁
流程如图:
相对于独占锁, 只是在方法中加了shared。
acquireShared()获取共享锁
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) {
// 当该节点的前驱节点是头结点且成功获取同步状态
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
releaseShared()释放共享锁
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
(3) LockSupport
//来看upparkSuccessor()方法
private void unparkSuccessor(Node node) {
//省略无关代码
LockSupport.unpark(s.thread);
}
public static void unpark(Thread thread) {
//省略无关代码
if (thread != null)
//委托给UNSAFE#unpark方法,这是一个本地方法
UNSAFE.unpark(thread);
}