8.4:AQS提供的模板方法
AQS使用模板模式,将一些与状态相关的核心方法开放给子类重写,而后AQS会使用子类重写的关于状态的方法进行线程的排队,阻塞以及唤醒等操作
在同步组件中,AQS是最核心的部分,同步组件的实现依赖AQS提供的模板方法来实现同步组件语义。
AQS实现了对同步状态的管理,以及对阻塞线程进行排队,等待通知等底层实现
1.独占锁:
1. void acquire(int arg) : 独占式获取同步状态,如果获取失败则将当前线程插入同步队列进行等待。
2. void acquireInterruptibly(int arg) : 与acquire方法相同,在1的基础上增加响应中断。
3. boolean tryAcquireNanos(int arg,long nanosTimeout) : 在2的基础上增加了超时等待功能,在规定时间内没有获得同步状态返回false
4. boolean tryAcquire(int arg) : 获取锁成功返回true,否则返回false
5. boolean release(int arg) : 释放同步状态,该方法会唤醒在同步队列中的下一个节点。
2.共享式锁:(比独占锁多了一个share)
1. void acquireShared(int arg) : 共享式获取同步状态,与独占锁的区别在于同一时刻有多个线程获取同步状态。(忽略模板)
2. void acquireSharedInterruptibly(int arg) : 增加了响应中断的功能
3.boolean tryAcquireSharedNanos(int arg,lone nanosTimeout) : 在2的基础上增加了超时等待功能
4. boolean releaseShared(int arg) : 共享锁释放同步状态。
3.同步队列(获取锁失败的队列)
AQS中的同步队列是一个带有头尾节点的双向链表,节点的组成为
1. int waitStatus; // 节点状态
2. Node prev; // 当前节点的前驱节点
3. Node next; // 当前节点的后继节点
4. Thread thread; // 当前节点所包装的线程对象
5. Node nextWaiter; // 等待队列中的下一个节点
将线程封装为Node节点后进行入队与出队处理
节点的状态如下:
1. int INITIAL = 0; // 初始状态
2. int CANCELLED = 1; // 当前节点从同步队列中取消
3. int SIGNAL = -1; // 后继节点的线程处于阻塞状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程继续运行。
4. int CONDITION = -2; // 节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了
signal()方法后,该节点将会从等待队列中转移到同步队列中,加入到对同步状态的获取中。
5. int PROPAGATE = -3; // 表示下一次共享式同步状态获取将会无条件地被传播下去。
8.5深入理解AQS
8.5.1独占锁
独占锁的获取:acquire (int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- tryAcquire再次使用CAS尝试获取同步状态,若成功方法直接返回,当前线程置为持有锁线程,若再次尝试失败,调用addWaiter()
2.addWaiter()源码--将当前线程封装为Node节点后尾插入同步队列
private Node addWaiter(Node mode) {
//将当前线程以指定模式( 独占式或共享式)封装为Node节点
Node node = new Node(Thread.currentThread(), mode);
//拿到当前队列的尾节点
Node pred = tail;
if (pred != null) {
node.prev = pred;
//将当前节点使用CAS尾插入同步队列
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//当前队列为空或CAS尾插失败时
enq(node);
return node;
}
3.enq():当同步队列为空时,完成队列的初始化操作以及不断CAS将当前节点尾插入同步队列
private Node enq(final Node node) {
//死循环,不断自旋
for (;;) {
//拿到尾节点
Node t = tail;
//当前节点为空
if (t == null) {
//完成队列的初始化操作,懒加载模式
if (compareAndSetHead(new Node()))//头节点,不放数据,只是一个起始位置
tail = head;
} else {
node.prev = t;
//不断将当前节点使用CAS尾插入同步队列中直到成功为止
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
4.acquireQueued()
final boolean acquireQueued(final Node node, int arg) {
//设置失败状态,初始化为true
boolean failed = true;
try {
//设置中断状态,默认为false;
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 {
//将当前节点置为取消状态,node.waitStutas=1;
if (failed)
cancelAcquire(node); }}
节点从同步队列获取同步状态的前提条件:
只有当前驱节点为头节点时,线程才有机会获取同步状态
if (p == head && tryAcquire(arg))
将当前节点通过setHead()方法设置为队列的头结点,然后将之前的头结点的next域设置为null并且pre域也为null,即 与队列断开,无任何引用方便GC时能够将内存进行回收。
示意图如下:

5.当前线程获取同步状态失败时,首先用shouldParkAfterFailedAcquire(Node pred, Node node)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) //当前节点是否应该被停止{
//获取前驱节点状态
int ws = pred.waitStatus;
//状态为-1,当前节点的后继节点处于等待状态就是node
if (ws == Node.SIGNAL)
//表示应该将当前节点阻塞
return true;
//前驱节点状态被取消
if (ws > 0) {
//一直向前找到节点状态不是取消状态的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//将前驱节点状态置为signal,-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
Pred:前驱节点 ws:前驱节点状态, Node.SIGNAL:需要更新后的值
shouldParkAfterFailedAcquire()尝试将前驱节点状态改为Node.SIGNAL,表示此时当前节点应该被阻塞;否则一直在
6.private final boolean parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt()
{
//将当前线程阻塞
LockSupport.park(this);
return Thread.interrupted();
}
独占式锁的获取过程:

独占式锁的释放--release()
1.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():提供给子类覆写
2.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);
}
本文详细解析了AQS(AbstractQueuedSynchronizer)的工作原理,包括独占锁与共享锁的获取与释放流程,以及同步队列的管理机制。AQS是Java并发包中的核心组件,用于构建各种同步器。
1073

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



