解析:很明显上面的addWaiter方法中出现了添加新节点到同步队列的逻辑,而在之后的enq方法中再次出现,
主要目的就是为了能在执行enq方法之前可以先进行一次尝试,看能否一次执行成功,若成功,则皆大欢喜,
不必走下面的逻辑,若不成功,再走enq方法,来通过无限循环的方式强制执行成功。所以前面的逻辑可以看成是一次简单的enq操作。
acquireQueued方法源码:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { 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,这里去除以前的节点对当前节点的引用,当前节点对象不再被使用后可以被GC清理 failed = false;// 表示成功 return interrupted; } // 如果前置节点不是头节点,或者当前节点线程未获取到同步状态,则将尝试将前置节点状态更新为SIGNAL,并阻塞当前线程 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } }解析:以无限循环的方法自旋,每次循环都尝试独享式获取同步状态,如果获取到了同步状态,
那么将当前节点置为头节点;如果前置节点不是头节点或者未获取到同步状态则尝试将前置节点的状态更新为SIGNAL,并阻塞当前线程(park),
这种情况下,当前线程需要被唤醒才能继续执行,当被唤醒之后可以再次循环,尝试获取同步状态,如果不成功,将会再次阻塞,等待再次被唤醒。
AbstractQueuedSynchronizer方法源码:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus;// 获取前置节点的状态 if (ws == Node.SIGNAL) // 表示后置线程节点(当前节点需要被唤醒) return true; if (ws > 0) { // 表示前置节点线程被取消,那么清理被取消的线程节点 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. */ // 尝试将前置节点的状态置为SIGNAL,只有置为SIGNAL之后才能返回true. compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } }解析:这个方法主要目的就是为了将前置节点状态置为SIGNAL,这个状态意思是它后面的那个节点被阻塞了,
需要被唤醒,可见这个状态就是一个标记,标记着后面节点需要被唤醒。
parkAndCheckInterrupt方法源码:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { private final boolean parkAndCheckInterrupt() { LockSupport.park(this);// 阻塞当前线程 return Thread.interrupted(); } }解析:一旦线程执行到这一步,那么当前线程就会阻塞,后面的return暂时就不会执行。只有在被唤醒之后才能接着返回中断校验的结果。
总结:acquire方法首先尝试独享式获取同步状态(tryAcquire),获取失败的情况下需要将当前线程封装成为一个Node节点,
然后首先尝试将其设置为同步队列的为节点,如果失败,则自旋直到成功为止,然后进行自旋判断当前节点是否第二节点,如果是,
则尝试获取同步状态,如果成功,将当前节点置为头节点;否则如果当前节点不是第二节点,或者获取同步状态失败,
则将前置节点状态置为SIGNAL,然后阻塞(park)当前线程,等待被唤醒,唤醒之后会重复自旋,判断节点是否第二节点和尝试获取同步状态,
如果还不成功,那么就再次阻塞...