AQS的常用方法----本次是非公平锁为例(ReentrantLock中的NonfairSync)
Lock方法
当我们使用ReentrantLock的lock方法进行加锁时,实际上是使用了其内部类(Sync)的 sync.lock()方法进行加锁操作。可以追溯到一个抽象的lock方法,本次以非公平锁为例。
JDK8的源码
final void lock() {
//初始化state的默认值为0
//通过CAS比较替换的方式将state的值设置为1
if (compareAndSetState(0, 1))
//获取当前的线程,将独占模式同步的当前所有者(可以理解为工作区)设置为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
//通过CAS比较替换的方式不能成功修改state的值时进入
acquire(1);
}
接下来我们通过对应的方法带入一个例子。前提是了解过主要相关的参数。
- 第一次进入lock方法,此时对应的线程为A通过CAS将同步状态state修改为1。并将自己放入了工作区中,方法结束。看看流程图
acquire方法
acquire方法的传参默认为1,实际上调用的是其父类的(AbstractQueuedSynchronizer)中的acquire方法。其中的核心方法有3个组成
tryAcquire(arg)
addWaiter(Node.EXCLUSIVE), arg)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire方法
tryAcquire方法最终调用了nonfairTryAcquire方法,传参默认为1。
先分析方法
final boolean nonfairTryAcquire(int acquires) {
//获取到当前线程
final Thread current = Thread.currentThread();
//获取到同步状态
int c = getState();
if (c == 0) { //表示当前的工作区可用即无任何线程
//通过CAS将同步状态state修改为1
if (compareAndSetState(0, acquires)) {
//将当前线程放入工作区中
setExclusiveOwnerThread(current);
return true;
}
}
//获取到工作区中的线程和当前线程进行比较
//可以看出ReentrantLock是可重入锁通过修改state表示同步次数
else if (current == getExclusiveOwnerThread()) {
//将state的同步表示加上传参的值
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//设置新的同步标识值
setState(nextc);
return true;
}
return false;
}
- 此时线程B进入了Lock方法,不会进入if块中,因为同步状态的标识被线程A改为了1,工作区的线程是A不是线程B。最终会调用到我们刚刚分析的这个方法。看以看出条件都不会满足,直接返回false。接下来看看addWaiter(Node.EXCLUSIVE), arg)做了什么??此时的流程图还是没有什么太大的变化。
addWaiter方法
该方法有两个参数分别是Node.EXCLUSIVE类和arg,分别是排他模式的一个Node类和参数1。分析源码。
private Node addWaiter(Node mode) {
//获取线程和对应的标记的模式封装成一个Node
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//将Node的尾部赋值给了一个变量
Node pred = tail;
//判断是否不为null
if (pred != null) {
//将pred赋值给了node节点的prev上一个引用
node.prev = pred;
//通过CAS比较Node的Tail(尾部)
if (compareAndSetTail(pred, node)) {
//把当前node赋值给了pred的下一个引用
pred.next = node;
return node;
}
}
//分析enq方法
enq(node);
return node;
}
private Node enq(final Node node) {
//死循环,即满足条件的自旋
for (;;) {
//将尾部节点赋值给了变量t
Node t = tail;
//非空判断
if (t == null) { // Must initialize
//通过CAS初始化Head节点
if (compareAndSetHead(new Node()))
//将初始化的头部节点赋值给了尾部节点
tail = head;
} else {
//将变量t赋值给了node节点的前一个引用
node.prev = t;
//通过CAS将尾部节点修改为node节点
if (compareAndSetTail(t, node)) {
//把t的下一个引用指向了node
t.next = node;
return t;
}
}
}
}
- 此时我们知道nonfairTryAcquire()方法返回的是一个false,经过!tryAcquire(arg)运算为true,此时进入了addWaiter(Node.EXCLUSIVE), arg)这个方法。开始分析。
- 将线程B封装成了一个Node节点,将尾部节点赋值给了变量pred,pred此时是为null的,经过判断我们得知进入了enq(final Node node)这个方法。因为AQS队列是没有数据的。接下来分析enq(final Node node)这个方法。
- 可以看到里面是一个死循环(满足特定条件的自旋)。将尾部节点赋值给了变量t,判断成立进入compareAndSetHead(new Node())方法,直接在队列head创建了一个空的Node节点(虚节点),并将头部的节点赋值给了尾部(tail = head)。看看此时的流程图发生了什么变化。
- 创建了一个空的Node节点数据,作为队列的第一个。循环继续,条件并不满足。把尾部的节点赋值给了变量t,此时变量t是不为null的,进入了else块中。把t赋值给了Node节点的前一个引用,通过CAS将尾部节点修改成了Node节点,最后将Node节点赋值给了t的下一个引用,最后将t返回出去了,循环结束。看看流程图。
此时t是谁??
通过第一次循环我们创建了一个没有数据的Node节点。此时t就是没有数据(但是有初始化数据)的Node。
Node是谁??
Node是通过传参的B线程经过封装的Node。
- 总结 addWaiter(Node mode)方法做了什么?
将当前线程B封装成了一个Node节点,通过自旋在队列head中生成了一个只有初始化数据的Node节点。
1、将线程BNode节点通过prev指向了初始化节点
2、再将tail通过CAS修改为线程BNode节点
3、最后才是将初始化节点next指向了线程BNode节点。
addWaiter(Node mode)方法结束。
拓展
此时线程C来了,分析下执行流程。
- 看enq(final Node node)方法。获取到尾部节点赋值给变量t,即t为线程BNode节点,if不成立直接进入else块中。
1、将线程CNode节点的prev指向了t(即线程BNode节点)
2、通过CAS将尾部节点的引用指向线程CNode节点
3、将t(即线程BNode节点)的next指向了线程CNode节点
acquireQueued()方法
final boolean acquireQueued(final Node node, int arg) {
//定义变量为true
boolean failed = true;
try {
//定义变量为false
boolean interrupted = false;
for (;;) {
//当前节点是线程BNode节点
//得到当前节点的prev的节点赋值给变量p
final Node p = node.predecessor();
//p是否为head true
//再次尝试去获取锁,此时线程A还继续在使用,所以获取不到 false
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//p为初始化节点
//node是线程BNode节点
//获取节点的waitStatus进行比较得到boolean值
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 分析acquireQueued做了什么?
此时按照顺序读更容易理解。 有自旋。
1、传参的Node节点为线程BNode节点,arg为1。先获取到BNode节点的prev节点赋值给了变量p,(即初始化节点),进行了相关判断得知初始化节点是head节点。但是由于线程A占用了锁,导致线程B获取不到锁。从而进入shouldParkAfterFailedAcquire(p, node)方法。
3、逻辑与第一步相同,依旧进入shouldParkAfterFailedAcquire(p, node)方法。
5、进入parkAndCheckInterrupt()方法。逻辑就是将该线程进行休眠。
park()方法
禁止当前线程进行线程调度
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取到waitStatus值
int ws = pred.waitStatus;
//Node.SIGNAL为 -1
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
2、获取到初始化节点的waitStatus值赋值给了变量ws值为0。
第一个判断 ws == -1?false
第二个判断 ws > 0 ? false
进入compareAndSetWaitStatus方法,将初始化节点Node的waitStatus值赋值为-1。return false方法结束。
4、获取到初始化节点的waitStatus值赋值给了变量ws值为-1。
第一个判断 ws == -1?true.
return true 方法结束。
对应的流程图
从lock开始到线程Node节点创建,以及双向链表的形成,到线程的的休眠流程就结束了。
unlock()方法
调用到AbstractQueuedSynchronizer中的release(int arg)方法
先分析tryRelease(int releases)方法。
再分析unparkSuccessor(Node node)方法。
按照上面思路加上流程图进行分析,安装序号阅读。
public final boolean release(int arg) {
if (tryRelease(arg)) {
//将头部节点赋值给变量h
Node h = head;
//h不为null并且h的waitStatus不为0
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
- 3、方法返回值为true进入if块中,将head赋值给了变量h,此时h为初始化节点(虚节点)。此时h.waitStatus的值在shouldParkAfterFailedAcquire(Node pred, Node node)中的compareAndSetWaitStatus(pred, ws, Node.SIGNAL)方法将waitStatus修改成了-1。此时的if判断是成立的,进入if块中分析unparkSuccessor(h)方法,记住此时h为初始化节点(虚节点)。
protected final boolean tryRelease(int releases) {//传参为1
//获取到state 和 releases求减得到变量c
int c = getState() - releases;
//判断当前线程是否是不是工作区中的线程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
//定义一个变量tree初始值为false
boolean free = false;
if (c == 0) {
free = true;
//设置工作区中的线程为null
setExclusiveOwnerThread(null);
}
//将state值设置为变量c
setState(c);
return free;
}
- 1、A线程调用unlock() 方法进行解锁。会先进入tryRelease()方法,获取到state为1,传参releases为1,相减得到变量c,此时c为0。当前线程A和工作区中的线程是相同的不会发生异常。
- 2、定义了一个free变量为false,进入if块中奖free设置成了true,并且将工作区中的线程设置为了null,此时工作区没有任何线程。最后将state的值修改为0,方法返回值为true。
private void unparkSuccessor(Node node) {
//获取到node节点的waitStatus赋值给变量ws
int ws = node.waitStatus;
if (ws < 0)
//通过CAS将当前Node的waitStatus值修改为0
compareAndSetWaitStatus(node, ws, 0);
//将Node的next指向变量s
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
//把尾部节点赋值给变量t,如果变量t不为null并且不为当前Node节点,依次prev向前引用
for (Node t = tail; t != null && t != node; t = t.prev)
//t的waitStatus <= 0时,将t赋值给s
if (t.waitStatus <= 0)
s = t;
}
//s不为null时,将s节点的线程进行唤醒
if (s != null)
LockSupport.unpark(s.thread);
}
- 4、获取到初始化节点(虚节点)的waitStatus赋值给变量ws,ws此时为-1。
满足第一个if块,通过CAS将当前Node的waitStatus值修改为0,获取到Node节点的next引用节点赋值给变量s,此时变量s即线程BNode节点。
s此时是不为null的不会满足第二个if块。
满足第三个if块。接着调用了LockSupport.unpark(s.thread)方法。该方法将s节点的线程进行唤醒(发送许可证)操作,即B线程即将开始工作。 - 5、找到B线程休眠的方法
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
判断该线程是否被中断过,我们安装没有中断的逻辑进行分析,得知在acquireQueued(final Node node, int arg)。看源码
final boolean acquireQueued(final Node node, int arg) {
//定义变量为true
boolean failed = true;
try {
//定义变量为false
boolean interrupted = false;
for (;;) {
//当前节点是线程BNode节点
//得到当前节点的prev的节点赋值给变量p
final Node p = node.predecessor();
//p是否为head true
//再次尝试去获取锁,此时线程A还继续在使用,所以获取不到 false
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//p为初始化节点
//node是线程BNode节点
//获取节点的waitStatus进行比较得到boolean值
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
-
6、开始分析返回false,if块不成立,进入下次循环。获取线程BNode的prev的引用赋值给变量p,此时变量p为初始化节点(虚节点)。
if (p == head && tryAcquire(arg)) ,p是为head节点,进入tryAcquire方法。
tryAcquire方法说过,放源码直接分析。 -
8、返回值为true进入if块中,调用了setHead(Node node)做了什么?
1、将线程BNode节点设置为头节点
2、将Node节点的thread赋值为null
3、将Node节点prev指向为null
将初始化(虚节点)节点的next引用为null,方便进行GC。
将free属性设置为false,我们按照线程没有中断的逻辑所以返回值为false,退出自旋。finally块中的判断不成立,方法结束。
此时的线程BNode节点就变成了我们初始化的节点(虚节点)。
final boolean nonfairTryAcquire(int acquires) {
//获取到当前线程
final Thread current = Thread.currentThread();
//获取到同步状态
int c = getState();
if (c == 0) { //表示当前的工作区可用即无任何线程
//通过CAS将同步状态state修改为1
if (compareAndSetState(0, acquires)) {
//将当前线程放入工作区中
setExclusiveOwnerThread(current);
return true;
}
}
//获取到工作区中的线程和当前线程进行比较
//可以看出ReentrantLock是可重入锁通过修改state表示同步次数
else if (current == getExclusiveOwnerThread()) {
//将state的同步表示加上传参的值
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//设置新的同步标识值
setState(nextc);
return true;
}
return false;
}
- 7、获取到当前B线程,获取到同步状态,此时的state是为0的。if判断成立,进入if块,通过CAS将state修改为1,并且将线程B放入工作区中。方法返回值为true直接结束。
流程图
总结
到这就结束了,学习AQS对本人的帮助还是挺高的。慢慢也可以看得懂一点源码了,如有错误,欢迎指出。