条件队列

概述

条件队列就像烤面包机中通知"面包已烤好"的铃声.如果注意听着铃声,那么当面包烤好后可以立刻得到通知,开始吃面包.如果没有听见铃声,那么会错过消息通知,但回到厨房时还可以观察烤面包机的状态,如果已经烤好,就取出面包;没有烤好就再次留意铃声

"条件队列"这个名字来源于:它使得一组线程(称之为等待线程集合)能够通过某种方式来等待特定的条件为真.传统队列的元素是一个个数据,而与之不同的是,条件队列中的元素是一个个正在等待相关条件的线程.

正如每个java对象都可以作为一个锁,每个对象同样可以作为一个条件队列,并且Object中的wait,notify和notifyAll方法就构成了内部条件队列的API.对象的内置锁与其内部条件队列是相互关联的,要调用对象X中的条件队列的任何一个方法,必须持有对象X上的锁.这是因为"等待由状态构成条件"与"维护状态一致性"这两种机制必须被紧密地绑定在一起:只有能对状态进行检查时,才能在某个条件上等待,并且只有能修改状态时,才能从条件等待中释放另一个线程.

Object.wait会自动释放锁,并请求操作系统挂起当前线程,从而使其他线程能够获得这个锁并修改对象的状态.当被挂起的线程醒来时,它将在返回之前重新获取锁.从直观上来理解,调用wait意味着"我要去休息了,但当发生特定的事情时唤醒我",而调用通知方法就意味着"特定事件发生了"

一个条件队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BoundedBuffer<V> extends BaseBoundedBuffer<V> {
public BoundedBuffer(int capacity) {
super(capacity);
}

public synchronized void put(V v) throws InterruptedException {
while(isFull()){
// 如果资源满了,则挂起线程,等待notifyAll()
wait();
}
doPut(v);
notifyAll();
}

public synchronized V take() throws InterruptedException {
while(isEmpty()){
// 如果资源空的,则挂起线程,等待notifyAll
wait();
}
V v = doTake();
notifyAll();
return v;
}
}

显式的Condition对象

一个Condition和一个Lock关联在一起.Condition比内置条件队列提供了更丰富的功能:

  • 在每个锁上柯村仔多个等待
  • 条件等待可以是可中断的或不可中断的
  • 基于时限的等待
  • 公平或非公平的队列操作
    Condition对象会继承Lock对象的公平性
  • 每个Lock都可以有任意数量的Condition对象

Condition的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class ConditionBoundedBuffer<T> {
protected final Lock lock = new ReentrantLock();

private final Condition notFull = lock.newCondition();

private final Condition notEmpty = lock.newCondition();

@GuardedBy("lock")
private int tail, head, count;

@GuardedBy("lock")
private T[] items;

public BoundedBuffer(int capacity) {
this.items = (T[]) new Object[capacity];
}

public void put(T t) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await();
}
items[tail] = t;
if (++tail == items.length) {
tail = 0;
}
notEmpty.notify();
} finally {
lock.unlock();
}
}

public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notFull.await();
}
T x = items[head];
items[head] = null;
if (++head == items.length) {
head = 0;
}
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}

ConditionBoundedBudder的行为与BoundedBuffer相同,但它对条件队列的使用方式更容易理解.
通过将两个条件谓词分开并放到两个等待线程集中,Condition使其更容易满足单次通知的需求
signal比signalAll更高效.