signal和signalAll大同小异,本节我们来看下signal函数,signal核心调用的是doSignal,所以signal函数就不贴了:
//doSignal只做了一件事,将wait队列中的节点移出道aqs的等待队列中
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;//这里是个出队的过程,将对头从队列中移出
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
* 看作者注解,如果不能将waitStatus设置成0,表示该节点是取消的,返回false
* 这里有两种情况:
* 1)ws的值不是condition(-2),说明已经被改变了
* 2)cas失败,说明有另一个线程也同时在signal,
* 不管哪种情况该node一定会被唤醒
* 这里返回false,看上面函数的while进入循环,拿到下一个节点,继续
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//入aqs的等待队列
Node p = enq(node);
int ws = p.waitStatus;
//ws>0说明不是正常等待状态(一般是cancel),可以看下面的源码注解
//或者cas ws失败则唤醒node线程
//注意这里的p是node的前置节点,假如cas成功,说明前置节点也是在等待状态,那么就不需要唤醒node线程(因为前置都在等待,node也一定是等待,等走aqs的正常流程唤醒就好了)
//如果cas失败说明ws正在被其他线程修改,这里又有2种情况:
//1)ws被修改成1(cancel),那么唤醒线程没问题
//2)ws被修改成0,也就是p已经获得到锁,这样unpark后会让代码执行到acquireQueued()去cas尝试获取锁,以此来提高效率
//再看如果ws<=0表示正常等待状态,不去唤醒线程会不会有问题?
//那么线程会一直挂起直到被p唤醒,重新进入while (!isOnSyncQueue(node)) 这个时候一定在aqs的等待队列中,继续往下进入acquireQueued()去获取锁
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;