在第二章第5节已经说了Conditon, 当时用Condition做了一个有界队列。
Condition是Lock的附属物,Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。
一个锁可以搞多个Condition
Condition的API
public interface Condition {
/**
* 让当前线程进入等待状态直到被通知或者被中断,当前线程重新进入运行状态且从await方法返回的情况如下
* 1. 其他线程调用了该Condition的signal 或signalAll
* 2. 其他线程调用interrupt中断了当前线程
* @throws InterruptedException
*/
void await() throws InterruptedException;
/**
* 等价上面那个
* @param time
* @param unit
* @return
* @throws InterruptedException
*/
boolean await(long time, TimeUnit unit) throws InterruptedException;
/**
* 指定到某个时间还没被通知且没被中断直接返回,提前被通知了返回true,否则false
* @param deadline
* @return
* @throws InterruptedException
*/
boolean awaitUntil(Date deadline) throws InterruptedException;
/**
* 唤醒等待在Condition上的一个线程
*/
void signal();
/**
* 唤醒所有等待在Condition上的线程
*/
void signalAll();
}
基本原理
在ReentrantLock里边搜newCondition
// 我们拿Condition都是通过Lock的newCondition接口拿的
// 但是我们发现他是掉的AQS的newCondition方法
public Condition newCondition() {
return sync.newCondition();
}
// 在找到AQS的newCondition,发现这个是自己写的
final ConditionObject newCondition() {
return new ConditionObject();
}
可见ConditionObject应该是实现了Condition接口,去看一眼
发现ConditionObject是AQS的内部类
等待队列
去看ConditionObject, 会发现里边也和AQS同步队列类似,有一个队列结构,队列节点居然和之前说的同步队列的节点是一样的,世间真奇妙
ConditionObject里边有两个重要成员变量firstWaiter,lastWaiter,对比AQS,AQS有head和tail,这个是双向队列的模板,太巧了
当前线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 构建了一个Node放到等待队列的队尾
Node node = addConditionWaiter();
// 我没仔细看,这里大概的意思是会把这个节点从同步队列移除,并且释放锁
// 因为调用await是获取锁的
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);
}
简单理解,await就是根据当前线程信息构建Node节点,加入等待队列,同时由于调用await的一定是获取锁的,所以我们要移除当前线程在同步队列的那个节点,其实就是首节点,首节点要变,首节点的线程就要释放锁,其实就是当前线程会释放锁。
上边这段描述是我看简单搂了一眼源码写的,发现竟然和书上意思一样,开心~
接下来就是看signal了,感觉能猜到他是怎么做的了
public final void signal() {
// 必须要获取锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
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 (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// enq前面分析过,就是同步队列的入队
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 很关键,唤醒
LockSupport.unpark(node.thread);
return true;
}
``
做了几个事,等待队列首节点出队,将这个首节点入队到同步队列,唤醒这个首节点的线程,唤醒之后就会去竞争锁(或者叫同步状态)了
await方法里面的while (!isOnSyncQueue(node)), 将会不满足条件了,所以await方法会返回
可以发现调用了signal 不是await的地方会立即执行的,需要先抢到锁才会继续执行
signalAll就是把等待队列中的所有节点都转移到同步队列,并唤醒
```java
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}