aqs读了好久,一直磕磕绊绊,还是自己捋一遍记忆比较深刻。
Node数据结构
- waitstatus
waiteStatus | 含义 |
---|---|
0 | 当一个Node被初始化的时候的默认值 |
CANCELLED | 为1,表示线程获取锁的请求已经取消了 |
CONDITION | 为-2,表示节点在条件队列中,节点线程等待唤醒 |
PROPAGATE | 为-3,当前线程处在SHARED情况下,该字段才会使用 |
SIGNAL | 为-1,表示线程已经准备好了,就等资源释放了 |
volatile status
我们可以通过修改State字段表示的同步状态来实现多线程的独占模式和共享模式(加锁过程)
- prev、next
这里主要在阻塞队列中使用(阻塞队列是一个双向链表)
- nextWaiter
在条件队列中使用(单向链表)
加锁过程
这里是通过ReentrantLock的非公平锁来分析加锁过程的。
且jdk版本为openJdk11。
//java.util.concurrent.locks.ReentrantLock
public void lock() {
//这里调用的是aqs抽象类的模板方法
this.sync.acquire(1);
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquire(int arg) {
//这里的主要逻辑,当尝试获取锁不成功的话,会将当前线程加入阻塞队列中
if (!this.tryAcquire(arg) && this.acquireQueued(this.addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg)) {
selfInterrupt();
}
}
注:这个模板方法要记好,因为整个加锁流程是按这个大方向走的。
这里就先按非公平锁的过程走一遍。
//java.util.concurrent.locks.ReentrantLock
protected final boolean tryAcquire(int acquires) {
return this.nonfairTryAcquire(acquires);
}
//java.util.concurrent.locks.ReentrantLock.NonfairSync
@ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {
Thread current = Thread.currentThread();//获取当前线程
int c = this.getState();//获取当前state值
//如果state值为0,说明当前没有线程申请锁,这样作为非公平锁就可以
//直接申请锁,不用管阻塞队列是否有线程等待。
if (c == 0) {
//通过cas获取锁(即将state修改为acquires)
if (this.compareAndSetState(0, acquires)) {
//获取锁成功后,可以将exclusiveOwnerThread设置为当前线程
//这将为可重入锁提供判断依据
this.setExclusiveOwnerThread(current);
return true;
}
//这里就是判断获取锁的线程和当前申请锁的线程是否为同一个线程,实现锁的可重入
} else if (current == this.getExclusiveOwnerThread()) {
//重入一次,state值加一
int nextc = c + acquires;
if (nextc < 0) {
throw new Error("Maximum lock count exceeded");
}
this.setState(nextc);
return true;
}
return false;
}
不行,我感觉还要贴一下那个模板方法:
//java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquire(int arg) {
//这里的主要逻辑,当尝试获取锁不成功的话,会将当前线程加入阻塞队列中
if (!this.tryAcquire(arg) && this.acquireQueued(this.addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg)) {
selfInterrupt();
}
}
如果tryAcquire(arg)
获取锁成功,那acquire方法结束,加锁失败才是最复杂的。
这里我们先看addWaiter
方法。
//java.util.concurrent.locks.AbstractQueuedSynchronizer
private AbstractQueuedSynchronizer.Node addWaiter(AbstractQueuedSynchronizer.Node mode) {
AbstractQueuedSynchronizer.Node node = new AbstractQueuedSynchronizer.Node(mode);
AbstractQueuedSynchronizer.Node oldTail;
do {
while(true) {
//获取旧的尾节点
oldTail = this.tail;
//如果尾节点不为null,将该node设置为尾节点
if (oldTail != null) {
//这条语句我猜测是node.prev = oldTail的意思,如果理解错误,还请指出
node.setPrevRelaxed(oldTail);
//跳出内层while循环
break;
}
//如果为空,就先初始化阻塞队列,就是创造一个空的头结点,
//使头结点和尾节点同时指向这个空节点(这里的空节点不是null节点,
//不要理解错误,有兴趣可以看看这个方法,没有几行)
this.initializeSyncQueue();
}
//使用cas将tail设置为node节点,设置成功跳出外层循环
} while(!this.compareAndSetTail(oldTail, node));
//最后将旧的尾节点的next指向新的尾节点,这里也有一个注意点,下一篇会讲到
oldTail.next = node;
//将node节点返回(也就是将尾节点返回给acquireQueued方法)
return node;
}
这里画了个图,帮助理解一下这个过程:
final boolean acquireQueued(AbstractQueuedSynchronizer.Node node, int arg) {
boolean interrupted = false;
try {
while(true) {
//获取node的前驱节点
AbstractQueuedSynchronizer.Node p = node.predecessor();
//如果node的前驱节点为head,并且尝试获取锁成功,则将当前node设置为head
if (p == this.head && this.tryAcquire(arg)) {
this.setHead(node);
p.next = null;
//唯一出口,当挂起的线程唤醒后,后继续执行while循环,当满足if条件的时候,会返回。
return interrupted;
}
//如果不满足上面的if语句,则会执行下面这段代码主要做了以下几件事(本来想直接在分析
//shouldParkAfterFailedAcquire方法时再说,但是怕大家连贯不起来):
//1.前驱节点的waitstatus==1直接返回true
//2.剔除掉已经取消的节点
//3.将node的前驱节点赋值为-1,返回false,这时这里的while循环会在执行一次,就会变成1的情况
if (shouldParkAfterFailedAcquire(p, node)) {
//将当前线程挂起,并记录中断状态,以便返回给上层调用函数
interrupted |= this.parkAndCheckInterrupt();
}
}
} catch (Throwable var5) {
//这里我看了一下,貌似只有两处会抛出异常
//1.node.predecessor();会抛出空指针异常
//2.this.tryAcquire(arg);会抛一个Error
this.cancelAcquire(node);
if (interrupted) {
selfInterrupt();
}
throw var5;
}
}
shouldParkAfterFailedAcquire
方法在上面已经介绍的差不多了,为了完整,我再贴一遍。
private static boolean shouldParkAfterFailedAcquire(AbstractQueuedSynchronizer.Node pred, AbstractQueuedSynchronizer.Node node) {
//获取前驱节点的waitStatus值
int ws = pred.waitStatus;
//如果等于-1就直接返回true
if (ws == -1) {
return true;
} else {
//否则,判断前驱节点是否取消(CANCEL值为1)了
if (ws > 0) {
//如果前驱节点取消了,就往前找,直到找到一个ws <= 0的
do {
node.prev = pred = pred.prev;
} while(pred.waitStatus > 0);
pred.next = node;
} else {
//将前驱节点的waitstatus值设置为-1
pred.compareAndSetWaitStatus(ws, -1);
}
return false;
}
}
在这里我想多说一句,看这一个方法的时候,也要把控一下全局,有时候可能一时理解不了,因为没有结合上下文来看,思路很有可能就会被堵住,我就是这样:sob:。
继续继续,如果shouldParkAfterFailedAcquire返回true,就该执行parkAndCheckInterrupt方法了。
private final boolean parkAndCheckInterrupt() {
//将当前线程挂起(注意,中断也可以将park掉的线程给唤醒)
LockSupport.park(this);
//将中断状态返回
return Thread.interrupted();
}
再回来看这个模板方法:
public final void acquire(int arg) {
if (!this.tryAcquire(arg) && this.acquireQueued(this.addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg)) {
//如果acquireQueued返回true的话,代表加锁过程有中断,需要将中断状态延续下去
selfInterrupt();
}
}
参考文章:
https://www.javadoop.com/post/AbstractQueuedSynchronizer
https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html