在JUC原理一篇了解了JUC的都是围绕着AbstractQueuedSynchronizer(简称AQS)实现的,也就是说AbstractQueuedSynchronizer为JUC的核心,本篇就是探索AbstractQueuedSynchronizer的源码。
在开始阅读源码之前再来梳理一下JUC的原理,那么在阅读源码时可以提高效率。
AQS的两个队列
在AQS中存在两个队列用于对线程的状态保存和记录,分别是同步队列和条件队列。
同步队列中的线程可以处于独占和共享两种状态,并且通过队列是一个双端队列。其结构如下:
而条件队列中的线程状态必须为独占模式,线程在获取锁时不满足条件,则将线程添加至条件队列并等待唤醒。当条件队列中的线程被唤醒后,将线程从条件队列中剔除并添加至同步队列而等待执行,条件队列是单向链表结构,结构如下:
在AQS中,实现队列的是通过AbstractQueuedSynchronizer类中的一个静态内部类Node实现的:
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
}
}
具体的字段和方法分析在下面详细解释。
AQS的工作流程
AQS的工作机制流程如下:
这个的流程是独占锁的工作流程,流程如下:
AQS的原理机制及流程介绍完了,接下来来分析AQS相关的源码。当然本篇暂时不介绍条件队列的流程。
首先从AQS中实现同步队列和条件队列的Node类说起。首先来介绍下其相关的属性及作用。
独占和共享的标识。
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
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;
CANCELLED :值为1。当线程等待超时或者被中断,则取消等待,设等待状态 为-1,进入取消状态则不再变化。
SIGNAL : 值为-1。后继节点处于等待状态,当前节点(为-1)被取消或者中断 时会通知后继节点,使后继节点的线程得以运行。
CONDITION值为-2。当前节点处于等待队列,节点线程等待在Condition上,当 其他线程对condition执行signall方法时,等待队列转移到同步队列,加入到对同步状态的获取。
PROPAGATE:值为-3。与共享模式相关,在共享模式中,该状态标识结点的线程处 于可运行状态。
指定线程的等待状态。
volatile int waitStatus;
实现队列的前置指针和后置指针。
volatile Node prev;
volatile Node next;
节点持有的线程。
volatile Thread thread;
条件队列单向指针。
Node nextWaiter;
当然这个属性有两个用途,在条件队列中该属性为指向下一个节点的指针,当在同步同列中用于表示当前节点的线程是独占模式还是共享模式。
判断当前线程是独占还是共享。
final boolean isShared() {
return nextWaiter == SHARED;
}
获取当前节点的前置节点。
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
构造方法。
Node() {
// Used to establish initial head or SHARED marker
}
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;
}
第一个无参构造,第二个是根据线程和锁占有模式构造独占节点还是共享节点。第三个指定节点的状态。
Node类的属性和方法相对来说没有那么复杂。接下来来梳理AbstractQueuedSynchronizer的主要方法,这里主要是独占锁相关的方法(共享锁的相关方法大同小异)。
这里梳理的属性和方法均是独占锁相关的属性和方法,同时并没有对可中断和指定时间等待方法做讲解,当然一下方法熟悉后那些功能也容易理解多了。
记录同步队列中头节点和尾节点属性。
private transient volatile Node head;
private transient volatile Node tail;
等待队列的头节点,懒加载模式,除去初始化,只能在setHead中修改
注意:如果头结点存在,它的状态waitSattus不能为CANCELLED,即1。
等待队列的尾部,懒加载模式,只能在enq方法中新增加等待节点时修改。
资源状态。
同步状态,用于统计获取锁的次数。
状态获取和修改方法。
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
CAS设置state状态。
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
节点入队方法。
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;
}
}
}
}
将参数节点追加到同步队列中,该方法返回队列中原尾部节点。
1>如果尾部节点为空,则初始化头部和尾部节点,新建一个Node实例(该Node实例 的waitStatus为int默认值为0),将head和tail指向该Node实例,然后将参数节点追加到新建的队列中。
2>如果尾部节点不为空,队列存在,则将参数节点追加到队列中。
该方法通过循环和CAS算法保证数据正确
向同步队列中增加节点。
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;
}
1>根据参数mode常见指定模式的节点。
2>判断tail尾部节点是否问空,不为空尝试添加至同步队列,成功则返回新建的节点。
3>不成功,则调用enq方法入队。
4>返回新建节点。
设置头节点。
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
将head指向参数节点,并将参数节点的线程置为空,将参数节点前置节点置为空。该方法是用在独占锁获取到锁之后,将获取到锁的当前节点这是为头节点。
唤醒后置节点。
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);
}
唤醒参数的后续节点。
1>参数节点的waitSatus<0,则将其waitSatus修改为0。
2>判断参数节点的后置节点waitStatus是否是<=0,如果是则唤醒。
3>如果不是,则从同步队列中由尾部向前遍历,查找最靠前的满足thread不为空并且 waitStatus<=0的几点,将其线程唤醒。
该方法用于在锁资源释放后然后调用该方法唤醒后续的线程。
取消抢占资源。
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
1>忽略空节点。
2>参数节点线程置为空。
3>由给定节点向前遍历找到第一一个没有被取消的节点(pred)。
4>如果参数节点为tail,则将tail指向pred并将pred指向null结束。
5>如果参数节点不是tail,判断pred不是头节点,并且pred的状态为SIGNAL或者修改SIGNAL成功,则将参数节点后的节点追加到pred后,如果不满足则唤醒后续节点。
主要就是将队列中的取消节点剔除,同时判断后继节点是不是追加到了head节点之后,如果是则就需要唤醒了,因为唤醒线程就是从头结点往后遍历唤醒的。
判断当前线程是否满足挂起的条件。
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;
}
1> 参数节点node的前置节点pred的waitStatus为SIGNAL,则返回true,证明当前 线程可以被阻塞,返回true。
2> 如果前置节点pred的waitStatus>0则证明被取消,那么由参数节点node从队列中向前遍历,查找到第一个没有被取消的节点,将node追加到找到的节点。
3>如果节点pred的waitstatus不是SIGNAL,则将pred节点状态修改为SIGNAL。2和3均未空,即不满足阻塞条件,所以需要继续抢夺资源并不断更改队列中位于node之前的节点的状态。
该方法是在线程没有获取到锁时自旋转中一直调用的方法,因为一个线程被阻塞必须将其前置节点的状态修改为SIGNAL(因为唤醒时是判断状态为SIGNAL的),所以当前线程的前置节点状态没有修改为SIGNAL则当前线程会一直自旋转尝试将前置节点修改为SIGNAL。当然会存在这种情况:在当前线程一直循环修改前置节点状态时,前置节点获取到了锁,那么前置节点会将状态修改为0,则当前线程会继续循环,要么前置节点执行完毕,要么当前线程将前置节点状态修改为SIGNAL并阻塞当前线程。
自我中断。
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
该方法调用了Thread的interrupt()方法修改了线程的状态。
唤醒线程并判中断状态。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
AQS核心方法。
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);
}
}
该方法实现与sychronized的轻量级锁的自旋类似。
1>在循环不断判断是否队列中的第一个节点如果是则抢占资源,抢占成功则将参数节点置为head。
2>如果不满足上述条件则将参数节点的前置节点waitStatue置为SIGNAL,成功则通过调用LockSupprot的park方法阻塞当前线程。
3>如果不成功则一直循环尝试修改参数节点前置节点的waitStatus,指导成功阻塞当前线程。
注意:在修改前置节点的waitStatus时也会一直判断参数节点是否是同步队列中的第一个节点,如果是就会抢占资源的
4>在方法最后,如果线程被中断则调用10 cancelAcquire方法,将参数节点从同步队列中剔除,如果有必要唤醒后续节点。
该方法的逻辑就是AQS的工作流程的实现,包括自旋转、修改前置节点状态、判断线程中断状态以及取消抢占等。
抢占资源方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
如上述代码所示,先通过tryAcquire()方法抢占资源,抢占不成功则调用addWaiter创建新节点并通过acquireQueued方法入队如果线程被中断过,则从同步队列中剔除并中断自己。
释放资源。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
该方法为释放资源,如果当前线程多次进入锁,即state释放一次后仍不为0,则该方
方法返回false。
关于AbstractQueuedSynchronizer中未实现的几个方法:
protected boolean tryAcquire(int arg)
protected boolean tryRelease(int arg)
protected int tryAcquireShared(int arg)
protected boolean tryReleaseShared(int arg)
在AbstractQueuedSynchronizer中已经将所有的基础架构方法实现,那么通过这四个方法从而可以自己扩展实现不同的功能。
以上是介绍了AQS的独占模式的相关方法,下篇介绍其条件等待队列的运行机制及源码并发多线程之AQS源码分析(下)。