这里写目录标题
独占式获取acquire
void acquire(int arg)
/**
*这里的acquire方法目的就是在获取失败时,能够自旋
*这里的if判断就实现了这一功能
*如果想要产生自旋,那么就要同时满足if的两个条件
*1.tryAcquire(arg)方法是由执行线程调用的,
*如果这个方法失败,那么acquire方法可能会让线程排队(如果它还没有排队),直到其他线程释放它为止。
*2.同时,acquireQueued成功,就是当前获取失败的线程进入排队成功——也就是说当前线程被中断
*并且处在同步队列中。
*那么此时需要该节点所在的线程自旋。
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
/** 如果P指向头结点(同步队列中从头结点出队,也就是说,
如果按顺序唤醒,那么最先唤醒的一定是头结点)
并且头结点尝试获取同步状态成功。
那么将当前节点设置为头结点,
p重新定位到当前的头结点(也就是node的位置);
failed 如果是true表示从队列中获取节点失败,
此时我们已经从队列中获取了当前节点的信息,所以此时将failed设置为false。
此时acquireQueued返回值是false,说明在队列中获取线程同步状态没有遇到中断。
也就是说,不需要节点内的线程自旋。
*/
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
/**
*如果当前节点的前驱节点 的线程状态是SIGNAL,也就是需要唤醒当前节点时:
*如果唤醒失败,那么此时需要将当前节点的线程阻塞。
*也就是shouldParkAfterFailedAcquire返回true。
*此时我们希望当前节点的线程被中断。
*接下来执行中断操作:
*尝试中断当前线程,如果中断成功则parkAndCheckInterrupt()返回true。
**/
这是一个从同步队列中获取当前线程的同步状态失败时,判断中断是否成功。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
/*
无论如何都会执行这一步
当从同步队列中获取节点失败是,failed的值是true,那么就需要执行cancelAcquire方法,
终止尝试获取同步队列中的同步状态。
如果顺利从队列中获取当前线程的同步状态,那么久不需要执行停止尝试获取的操作。
*/
if (failed)
cancelAcquire(node);
}
}
全图
主干
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
shouldParkAfterFailedAcquire(p, node)
独占式释放release
boolean release(int arg)
使用release方法可以释放节点的同步状态。
public final boolean release(int arg) {
/*尝试release
如果成功并且头结点不为空&&头结点的state值不是0(也就是头结点不属于四种状态中的任何一种);
那么需要将后继节点解除阻塞(会唤醒后继节点,进而使后继节点重新尝试获取同步状态);
返回true。
如果尝试release失败,则直接返回false。
*/
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
总结
获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到同步队列中,
并且会在同步队列中自旋;
移出队列或停止自旋的条件是:前驱节点为头节点且成功获取同步状态。
p == head && tryAcquire(arg)。
释放同步状态时,同步器调用tryRelease(arg)方法释放同步器,然后unparkSuccessor(h)唤醒头结点的
后继节点。