static final int CONDITION = -2;
// 表示下一次共享模式同步状态获取讲会无条件地被传播下去
static final int PROPAGATE = -3;
// 即上面的CANCELLED/SIGNAL/CONDITION/PROPAGATE,初始状态为0
volatile int waitStatus; // 等待状态
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
volatile Thread thread; // 节点的线程(获取同步状态的线程)
// 条件队列(注意和同步队列区分)中的后继节点:参见addConditionWaiter方法,
// 表示下一个等待Condition的Node,如果当前节点是共享的,那么这个字段将是一个
// SHARED常量,也就是说节点类型(独占和共享)和等待队列中的后继节点共用一个字段。
Node nextWaiter;
final boolean isShared() { // 如果节点在共享模式下等待,则返回true。
return nextWaiter == SHARED;
}
// 返回节点的前驱节点,如果为null,则抛出NullPointerException
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // 用于创建头节点或SHARED标记
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
// 同步队列的头节点,使用懒汉模式初始化。 除了初始化,它只能通过setHead方法修改。
// 注意:如果头节点存在,其waitStatus保证不是CANCELLED。
private transient volatile Node head;
// 同步队列的尾节点,使用懒汉模式初始化。仅通过enq方法修改,用于添加新的等待节点。
private transient volatile Node tail;
// 同步状态, volatile修饰,很多同步类的实现都用到了该变量,
// 例如:ReentrantLock、CountDownLatch等
private volatile int state;
// 返回当前的同步状态
protected final int getState() {
return state;
}
// 设置同步状态值
protected final void setState(int newState) {
state = newState;
}
// 使用CAS修改同步状态值
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
AQS中Node是组成队列的数据结构,如下图是队列的数据结构图:
acquire方法
=========
public final void acquire(int arg) {
// tryAcquire(arg)方法:提供给子类实现的,主要用于以独占模式尝试acquire
if (!tryAcquire(arg) &&
// addWaiter方法:添加一个独占模式的节点到同步队列的尾部;
// acquireQueued:该节点尝试acquire
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt(); // 中断当前线程
}
-
首先是调用tryAcquire方法,在AQS中该方法是没有实现的,子类必须实现,主要用于以独占模式尝试acquire。例如在ReentrantLock中的实现逻辑是:先获取当前的同步状态,再使用CAS尝试将同步状态修改成期望值,如果修改成功将拥有独占访问权的线程设置为当前线程。在ReentrantLock中,acquire指的是获取锁,而tryAcquire即为尝试获取锁。
-
如果tryAcquire返回false,则尝试acquire失败了,则会调用addWaiter方法(详解见下文代码块1),添加一个独占模式的节点到同步队列尾部。 并调用acquireQueued方法(详解见下文代码块3)尝试acquire。
-
最后,如果acquireQueued返回true,则调用selfInterrupt方法中断当前线程,这是因为acquireQueued返回true就是代表线程被中断。
代码块1:addWaiter方法
private Node addWaiter(Node mode) {
// 以当前线程和mode为参数,创建一个节点
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail; // 将pred赋值为当前尾节点
if (pred != null) { // pred不为空
// 将新创建的节点的前驱节点设置为pred,即将刚创建的节点放到尾部
node.prev = pred;
// 使用CAS将尾节点修改为新节点
if (compareAndSetTail(pred, node)) {
// 尾节点修改成功后,将pred的后继节点设置为新节点,与上文node.prev=pred对应
pred.next = node;
return node;
}
}
// 如果pred为空,代表此时同步队列为空,调用enq方法将新节点添加到同步队列
enq(node);
return node;
}
根据当前线程和入参mode创建一个新的Node,并放到尾部。如果同步队列为空,则调用enq方法(详解见下文代码块2)添加节点。
代码块2:enq方法
// 将节点插入队列,如果队列为空则先进行初始化,再插入队列。
private Node enq(final Node node) {
for (;😉 {
Node t = tail; // 将t赋值为尾节点
// 如果尾节点为空,则初始化head和tail节点
if (t == null) {
// 使用CAS将头节点赋值为一个新创建的无状态的节点
if (compareAndSetHead(new Node()))
tail = head; // 初始化尾节点
} else { // 如果尾节点不为空,使用CAS将当前node添加到尾节点
node.prev = t; // 将node的前驱节点设置为t
if (compareAndSetTail(t, node)) { // 使用CAS将尾节点设置为node
// 成功将尾节点修改为node后,将t的后驱节点设置为node,与node.prev=t对应
t.next = node;
return t;
}
}
}
}
-
如果队列为空,则先初始化head和tail节点(介绍属性时说过了,head和tail采用懒汉模式初始化),再使用CAS将node添加到队列尾部。
-
如果队列不为空,直接使用CAS将node添加到队列尾部。
该方法和上面的addWaiter方法其实很相似,只是多了一个队列为空时的初始化head和tail操作。
代码块3:acquireQueued方法
// 添加完节点后,立即尝试该节点是否能够成功acquire
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false; // 用于判断是否被中断过
for (;😉 { // 自旋过程
final Node p = node.predecessor(); // 将p赋值为node的前驱节点
// 如果p为头节点,则node节点尝试以独占模式acquire(acquire一般为获取锁)
if (p == head && tryAcquire(arg)) {
// node节点成功以独占模式acquire,调用setHead方法将node设置为头节点
setHead(node);
p.next = null; // 断开原头节点与node节点的关联
failed = false;
return interrupted; // 返回node是否被中断过
}
// shouldParkAfterFailedAcquire: 校验node是否需要park(park:会将node的线程阻塞)
// 只有当前驱节点等待状态为SIGNAL,才能将node进行park,因为当前驱节点为SIGNAL
// 时,会保证来唤醒自己,因此可以安心park
if (shouldParkAfterFailedAcquire(p, node) &&
// node进入park状态,直到被前驱节点唤醒,被唤醒后返回线程是否为中断状态
parkAndCheckInterrupt())
interrupted = true; // 在等待过程中被中断
}
} finally {
if (failed)
cancelAcquire(node); // 取消正在进行的acquire尝试,走到这边代表出现异常
}
}
-
该方法用于添加完节点后调用,首先判断node节点的前驱节点是否为head,如果是,node会尝试acquire,如果node成功acquire,会调用setHead方法,将node设置为head、将node的thread设置为null、将node的prev设置为null,这保证了数据结构中头节点永远是一个不带Thread的空节点。
-
如果node节点的前驱节点不是head,或者node尝试acquire失败,则会调用shouldParkAfterFailedAcquire方法(详解见下文代码块4)校验node是否需要park(此处park是将node的线程阻塞,LockSupport.park),如果shouldParkAfterFailedAcquire返回true则调用parkAndCheckInterrupt方法(详解见下文代码块5)将node的线程阻塞。
-
如果走到finally方法时,failed为true,则代表出现了异常,调用cancelAcquire方法(详解见下文代码块6)取消正在进行的acquire尝试。
代码块4:shouldParkAfterFailedAcquire方法
// 判断节点是否应该park
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 前驱节点的等待状态
if (ws == Node.SIGNAL) // 如果前驱节点节点等待状态为SIGNAL,
return true; // 返回true,表示node节点应该park,等待它的前驱节点来唤醒
// 如果前驱节点的等待状态>0,代表该前驱节点为CANCELLED(取消)状态,需要跳过该节点
if (ws > 0) {
// 从pred节点开始向前寻找,直到找到等待状态不为CANCELLED的,
do {
node.prev = pred = pred.prev; // 将其设置为node的前驱节点
} while (pred.waitStatus > 0);
pred.next = node; // 与上面对应
} else {
// pred节点使用CAS尝试将等待状态修改为SIGNAL(ws必须为PROPAGATE或0),
// 然后返回false(即再尝试一次能否不park直接acquire成功),
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false; // 返回false,代表node还不能park
}
-
如果前驱节点pred的waitStatus为SIGNAL,返回true,表示node节点应该park,等待它的前驱节点来唤醒。
-
如果前驱节点pred的waitStatus>0,代表该节点为CANCELLED(取消)状态,需要跳过该节点。从pred节点开始向前寻找,直到找到等待状态不为CANCELLED的,将其设置为node的前驱节点。
-
否则,使用CAS尝试将pred节点的waitStatus修改为SIGNAL,然后返回false,这里直接返回false是为了再执行一次acquireQueued方法for循环的“if (p == head && tryAcquire(arg))”代码,因为如果能tryAcquire成功,则避免了当前线程阻塞,也就减少了上下文切换的开销(关于上下文切换等内容可以参考我的另一篇文章:Java并发:性能与可伸缩性)。
代码块5:parkAndCheckInterrupt方法
private final boolean parkAndCheckInterrupt() { // 等待,然后检查是否被中断
LockSupport.park(this); // 阻塞当前节点的线程
return Thread.interrupted(); // 返回当前线程是否为中断状态
}
调用LockSupport.park方法将当前线程阻塞,并在被唤醒之后,返回当前线程是否为中断状态。
代码块6:cancelAcquire方法
private void cancelAcquire(Node node) { // 取消正在进行的acquire尝试
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
// node的前驱节点pred的waitStatus如果为CANCELLED,
// 则向前寻找waitStatus不为CANCELLED的前驱节点pred
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 拿到pred的后继节点(不一定为node,请注意)
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED; // 将node的waitStatus设置为CANCELLED
最后总结
ActiveMQ+Kafka+RabbitMQ学习笔记PDF
关于分布式,限流+缓存+缓存,这三大技术(包含:ZooKeeper+Nginx+MongoDB+memcached+Redis+ActiveMQ+Kafka+RabbitMQ)等等。这些相关的面试也好,还有手写以及学习的笔记PDF,都是啃透分布式技术必不可少的宝藏。以上的每一个专题每一个小分类都有相关的介绍,并且小编也已经将其整理成PDF啦
ANCELLED; // 将node的waitStatus设置为CANCELLED
最后总结
ActiveMQ+Kafka+RabbitMQ学习笔记PDF
[外链图片转存中…(img-XYV6t8Cu-1719270965208)]
[外链图片转存中…(img-p1SgNE4X-1719270965209)]
[外链图片转存中…(img-nrYMHEfZ-1719270965209)]
[外链图片转存中…(img-oaAUD3X6-1719270965210)]
关于分布式,限流+缓存+缓存,这三大技术(包含:ZooKeeper+Nginx+MongoDB+memcached+Redis+ActiveMQ+Kafka+RabbitMQ)等等。这些相关的面试也好,还有手写以及学习的笔记PDF,都是啃透分布式技术必不可少的宝藏。以上的每一个专题每一个小分类都有相关的介绍,并且小编也已经将其整理成PDF啦