条件锁,参考http://ifeve.com/aqs-2/中条件锁实现的基础思路。在条件锁中,绝大多数时候,方法是处于单线程执行的过程(因为在使用条件锁的时候,必须获取排它锁)。
conditionLock的关键方法await():
API中的描述:
implements interruptible condition wait.
- If current thread is interrupted, throw InterruptedException.
- Save lock state returned by
getState
. - Invoke
release
with saved state as argument, throwing IllegalMonitorStateException if it fails. - Block until signalled or interrupted.
- Reacquire by invoking specialized version of
acquire
with saved state as argument. - If interrupted while blocked in step 4, throw InterruptedException.
第一步,如果线程有中断状态,则立刻抛出异常
第二步,保存state,这里的state实际上是获取排他锁的时候设置的状态,如果具体到实现ReentrantLock这里大多数应该是1
----------以上面完全是串行操作,是在拥有一个排它锁的状态下执行的---------
第三步,释放这个排它锁(首先检测当前线程是否获取到了排它锁,没获取到是会抛出异常的),让后驱节点的线程能够执行,进入到条件锁的await方法里面。
-------第三步操作会导致后驱节点的执行,由此,开始进入多线程操作的环境---------
第四步,进入block状态。这里是和条件锁的signal()/signalAll()有着密切关系,只有signal()/signalAll()调用,触发条件锁的Node转移到“CHL”队列的时候,这里的block状态才有机会解除(中断这种非正常情况不考虑了)。
第五步,当block状态解除的时候,这个时候需要检测“CHL”获取到一个排他锁,来执行后面的操作。(个人觉得如果只有signal()应该是完全不用再次尝试获取排他锁,因为signal方法只会触发某个节点的解除block状态;signalAll()则会触发所有的线程解除block状态,所以这个时候需要检测“CHL”队列来获取排它锁)
-------第五步,又让所有的操作开始进入串行化-----------------------
第六步,如果在block状态的时候发生了中断,抛出异常
在ConditionObject这个conditionLock实现类中,成员变量firstWaiter和lastWaiter中甚至没有使用volatile关键字。
一步一步说await方法:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
1.addConditionWaiter()方法调用,会创建一个新Node并且加入到conditionLock等待队列、剔除那些已经被取消的队列节点。
2.fullyRelease(node)会导致,“CHL”队列中的后驱节点上的线程被唤醒。(记住我们的前提,条件锁的使用是执行获取排它锁。也就是当前线程执行到这步,会有两个Node,一个在排它锁队列“CHL”,一个在条件锁的队列里面)。
3.isOnSyncQueue(node)这里,判断如果不在排它锁队列,则进入block,等待进入“CHL”队列机会(其他线程调用的signal/signalAll调用会给这个机会)
4.checkInterruptWhileWaiting(node),主要检查中断状态,为了实现 JSR133修订以后,就要求如果中断发生在signal
操作之前,await方法必须在重新获取到锁后,抛出InterruptedException
。但是,如果中断发生在signal
后,await
必须返回且不抛异常,同时设置线程的中断状态。
5.acquireQueued(node, savedState) 正常情况下,这里其实没多大用(因为该这个节点解除block,它正好会在头结点),主要是因为上一句红字部分,导致必须这样做,因为中断会导致block状态异常解除,这个时候又必须符合红字部分的情况(必须重新获取锁)。
。。。。后面几个方法作用,从名字看。
---------关键方法signal()调用的doSignal:
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
这里为什么可以直接调用,还是那个大前提,因为这个方法的调用,也是在获取到排它锁的情况下执行的。
transferForSignal(Node node)让在条件锁队列的Node,进入排它锁队列“CHL”队列,并且设置前驱节点的状态为Node.SIGNAL表示需要唤醒信号。
这里如果Node转移失败了,会唤醒Node中的线程。根据代码, transferForSignal失败的原因不知道为什么,反正JavaDoc说是无害的。