状态依赖性:当进行下一步操作时必须先满足某个条件。例如,需要取出队列中的元素,必须依赖的状态是队列不为空;需要将元素放入缓冲区,依赖的状态是缓冲区还有空间。
在并发程序中,几条指令前队列可能还是为空,现在却为非空。在并发程序中对状态的管理我们先提供这两种办法:
- 轮询。不断询问是否满足条件。例如需要取出缓冲区的元素时不断询问缓冲区是否为空
while (true){ if (condition is satisfied){ doSomething(); break; } }
while(true){ if (condition is satisfied){ doSomething(); break; } Thread.sleep(); //进行休眠:在CPU响应速度和CPU使用率之间做出权衡 }
- 阻塞。当发现条件不满足时阻塞,直到满足条件时才唤醒。
while (condition is not satisfied){ block(); } doSomething();
轮询需要消耗额外的CPU时钟周期;而阻塞则需要有中断机制,否则可能无法跳出循环,也就无法正确结束程序,中断机制可以由调用者编写(throws Exception将异常抛给调用者),或者额外实现(try-catch捕捉异常,但需要由外界触发中断)
更好的办法是使用条件队列。条件队列实现的功能是:将不能满足的线程挂起,而当某个条件成真时将线程立刻唤醒。
条件队列就像厨房中烤面包机中的铃声。如果注意听铃声,当面包机烤好面包后可以立刻得到通知并取出面包。如果没有听到铃声(可能出去拿报纸),那么会错过通知信息,回来之后可以进行检查,如果烤好了再取出面包,如果没有,再次留意铃声。
正如每个Java对象都可以作为一个锁,每个对象同样可以作为一个条件队列。这个对象的wait,notify,notifyAll就构成了内部条件队列的API。对象的内置锁与条件队列是相互关联的。要调用对象X中条件队列的任何一个方法,必须先持有对象X上的锁。
可以把条件队列理解为面包机,排队等待使用面包机的人就是线程(条件队列的元素),面包机的铃声就是notify,notifyAll方法,当有人正在使用面包机时,等待使用面包机的人就会调用wait()方法将自己阻塞(可以去做别的事情),然后等待面包机的铃声。当使用notify方法时,会随机选中一个人使用面包机;当使用notifyAll方法时,所有人都会听到铃声,然后争夺使用面包机的权利(谁的拳头大谁先用,或者谁先把面包放进去谁先用,具体取决于操作系统的调度方式)
来看个例子
以下是有界缓存的基类
public class BaseBoundedBuffer<V>{
private final V[] buf;
private int tail;
private int head;
private int count;
protected BaseBoundedBuffer(int capacity){
this.buf = (V[])new Object[capacity];
}
protected synchronized final void doPut(V v){
buf[tail] = v;
if (++tail == buf.length){
tail = 0;
}
count++;
}
protected synchronized final V doTake(){
V v = buf[head];
if (++head == buf.length){
head = 0;
}
--count;
return v;
}
public synchronized final boolean isFull(){
return count == buf.length;
}
public synchronized final boolean isEmpty(){
return count == 0;
}
}
public class BoundedBuffer<V> extends BaseBoundedBuffer<V>{
public BoundedBuffer(int size){
super(size);
}
public synchronized void put(V v) throws InterruptedException{
while (isFull()){
wait();
}
doPut(v);
notifyAll();
}
public synchronized V take()throws InterruptedException{
while (isEmpty()){
wait();
}
V v = doTake();
notifyAll();
return v;
}
}
上面这个BoundedBuffer就是一个条件队列, 调用了put()和take()的线程就是队列的元素,当不满足条件时,调用队列的wait()方法阻塞,监测到铃声时,调用队列的notifyAll()通知队列元素。