AQS 自定义同步锁,挺难的! (3)

acquireQueued方法在循环中会多次调用shouldParkAfterFailedAcquire,在等待队列中节点的waitStatus的属性默认为0,所以第一次执行shouldParkAfterFailedAcquire会执行:

compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

更新完pred.waitStatus后,节点的状态如下:

AQS 自定义同步锁,挺难的!

然后shouldParkAfterFailedAcquire返回false,回到acquireQueued的循环体中,又去抢锁还是失败了,又会执行shouldParkAfterFailedAcquire,第二次循环时此时的pred.waitStatus等于Node.SIGNAL那么就会返回true。

parkAndCheckInterrupt 阻塞线程

这个方法就比较直观了, 就是将线程的阻塞住:

private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); } 为什么是一个for (;;)无限循环呢

先看一个for (;;)的退出条件,只有node的前一个节点是head并且tryAcquire返回true时才会退出循环,否则的话线程就会被parkAndCheckInterrupt阻塞。

线程被parkAndCheckInterrupt阻塞后就不会向下面执行了,但是等到它被唤醒后,它还在for (;;)体中,然后又会继续先去抢占锁,然后如果还是失败,那又会处于等待状态,所以一直循环下去,就只有两个结果:

抢到锁退出循环

抢占锁失败,等待下一次唤醒再次抢占锁

线程 A 释放锁

线程A的业务代码执行完成后,会调用CustomLock.unlock方法,释放锁。unlock方法内部调用的release(1):

public void unlock() { release(1); }

release是AQS类的方法,它跟acquire相反是释放的意思:

public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }

方法体中的tryRelease是不是有点眼熟,没错,它也是在实现CustomLock类时重写的方法,首先在tryRelease中会判断当前线程是不是已经获得了锁,如果没有就直接抛出异常,否则的话计算state的值,如果state为0的话就可以释放锁了。

protected boolean tryRelease(int arg) { int state = getState() - arg; if(getExclusiveOwnerThread() != Thread.currentThread()){ throw new IllegalMonitorStateException(); } boolean free = false; if(state == 0){ free = true; setExclusiveOwnerThread(null); System.out.println("Thread: " + Thread.currentThread().getName() + "释放了锁"); } setState(state); return free; }

release方法只做了两件事:

调用tryRelease判断当前线程释放锁是否成功

如果当前线程锁释放锁成功,唤醒其他线程(也就是正在等待中的B线程)

tryRelease返回true后,会执行if里面的代码块:

if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; }

先回顾一下现在的等待队列的样子:

AQS 自定义同步锁,挺难的!

根据上面的图,来走下流程:

首先拿到head属性的对象,也就是队列的第一个对象

判断head不等于空,并且waitStatus!=0,很明显现在的waitStatus是等于Node. SIGNAL的,它的值是-1

所以if (h != null && h.waitStatus != 0)这个if肯定是满足条件的,接着执行unparkSuccessor(h):

private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next; ... if (s != null) LockSupport.unpark(s.thread); }

unparkSuccessor首先将node.waitStatus设置为0,然后获取node的下一个节点,最后调用LockSupport.unpark(s.thread)唤醒线程,至此我们的B线程就被唤醒了。

此时的队列又回到了,线程B刚刚入队的样子:

AQS 自定义同步锁,挺难的!

线程B 唤醒之后

线程A释放锁后,会唤醒线程B,回到线程B的阻塞点,acquireQueued的for循环中:

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); } }

线程唤醒后的第一件事就是,拿到它的上一个节点(当前是head结点),然后使用if判断

if (p == head && tryAcquire(arg))

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygwzg.html