从自定义独占锁OnlyOneLock去看AQS源码
先说明本文是基于JDK1.8的源码分析,其他版本的JDK实现可能有所不同。
在自定义锁中,我们覆盖Lock接口的方法,AQS中我们覆盖的几个方法只需要处理好拿到锁和没有拿到锁的逻辑,至于线程怎么维护,我们却不需要理会。
AQS如何实现获取锁的逻辑
我们自定义的独占锁OnlyOneLock,在我们重写了tryAcquire时,AQS的模板方法acquire内部会调用这个tryAcquire方法,然后维护队列逻辑。
OnlyOneLock类的tryLock方法
@Override
public boolean tryLock() {
return aqs.tryAcquire(1);
}
tryLock调用了AQS的tryAcquire方法。刚好我们在OnlyOneLockAQS类中重写了这个方法
@Override
protected boolean tryAcquire(int arg) {
//状态为0时,设置为1,表示当前线程获取到锁
if(compareAndSetState(0,1)){
//设置当前线程为获取到锁的线程
setExclusiveOwnerThread(Thread.currentThread());
return true ;
}
return false;
}
AQS的模板方法acquire内部会调用这个tryAcquire方法,然后维护队列逻辑。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //调用
tryAcquire成功获取锁的话,就结束 selfInterrupt(); //没有获取成功,调用addWaiter();把当前线程构建成一个Node加入到队列尾部。
}
Node.EXCLUSIVE是一个null结点。因为我们这里定义的是独占锁。
上面调用了addWaiter方法。把当前线程构建成一个Node加入到队列尾部。
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; //tail是原队列的尾结点
if (pred != null) { //原队列不为空
node.prev = pred; //新节点node的前置结点是pred(原队列的尾结点)
if (compareAndSetTail(pred, node)) { //连接新的尾结点
pred.next = node;
return node;
}
}
enq(node); //原队列为空的情况下,初始化队列
return node;
}
调用addWaiter方法后,会再调用acquireQueued。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); //获取node结点的前一个结点p
if (p == head && tryAcquire(arg)) { //如果p是首节点head并且node代表的线程获取锁成功
setHead(node); //新的队首
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //等待
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
acquireQueued方法可以总结如下:
一,先检查当前线程在队列中的前一个线程是否为头结点,如果是就用tryAcquire方法尝试再拿一次。
二,上面的尝试中没有拿到锁的话,会在parkAndCheckInterrupt时把自己给Park掉,Park大致可以理解为休眠(Sleep)
三,Park的时候,线程就老老实实的在队列里面等着,等什么呢?等获取锁的线程释放锁后通知它,它好继续在for循环里面获取锁。
自定义的独占锁AQS维护锁逻辑的大致过程流程图如下。
因为我们定义的是独占锁,如果线程请求锁直接成功,那它就不需要入队列。如果没有成功获取,就要入队列,队列的首节点永远是持有锁的那个结点。
AQS的内部类Node有一个重要的成员变量waitStatus。该变量用来表示当前节点代表的线程的状态。初始时为0。
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
以下是他的取值可能。
static final int CANCELLED = 1; //取消
static final int SIGNAL = -1; //该线程需要unpark
static final int CONDITION = -2; //等待某个条件
static final int PROPAGATE = -3;
OnlyOneLock重写的unlock调用了AQS的release方法。
@Override
public void unlock() {
aqs.release(1);
}
AQS会调用tryRelease方法,因为我们在OnlyOneLockAQS中重写了这个方法,所以执行时,调用的是我们重写后的tryRelease方法。
public final boolean release(int arg) {
if (tryRelease(arg)) { //如果释放锁成功
Node h = head; //队头
if (h != null && h.waitStatus != 0) //
waitStatus!=0说明,线程在等待获取锁 unparkSuccessor(h); //唤醒后继节点
return true;
}
return false;
}
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); //唤醒后继结点
}
参考:
https://juejin.im/post/5a3a09d9f265da4312810fb9
https://juejin.im/post/5a3c6aa551882538d3101d5f