请考虑这样一个饭店,它有一个厨师和一个服务员。这个服务员必须等待厨师准备好膳食。当厨师准备好时,他会通知服务员,之后服务员上菜,然后返回继续等待。这是一个任务协作的示例:厨师代表生产者,而服务员代表消费者。两个任务必须在膳食被生成和消费时进行握手,而系统必须以有序的方式关闭。下面时对这个叙述建模的代码:
import jdk.nashorn.internal.ir.CallNode;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class Meal1 {
private final int orderNum;
public Meal1(int orderNum) {
this.orderNum = orderNum;
}
public String toString() {
return "Meal1 " + orderNum;
}
}
class WaitPerson implements Runnable {
private Restaurant restaurant;
public WaitPerson(Restaurant restaurant) {
this.restaurant = restaurant;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
synchronized (this) {
while (restaurant.meal == null) {
wait();
}
}
System.out.println("Waitperson got " + restaurant.meal);
synchronized (restaurant.chef) {
restaurant.meal = null;
restaurant.chef.notifyAll();
}
}
}catch (InterruptedException e) {
System.out.println("WaitPerson interrupted");
}
}
}
class Chef implements Runnable {
private Restaurant restaurant;
private int count = 0;
public Chef(Restaurant restaurant) {
this.restaurant = restaurant;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
synchronized (this) {
while (restaurant.meal != null) {
wait();
}
}
if (++count == 10) {
System.out.println("Out of food, closing");
restaurant.executorService.shutdownNow();
}
System.out.println("Order up!");
synchronized (restaurant.waitPerson) {
restaurant.meal = new Meal1(count);
restaurant.waitPerson.notifyAll();
}
TimeUnit.MILLISECONDS.sleep(100);
}
}catch (InterruptedException e) {
System.out.println("Chef interrupted");
}
}
}
public class Restaurant {
Meal1 meal;
ExecutorService executorService = Executors.newCachedThreadPool();
WaitPerson waitPerson = new WaitPerson(this);
Chef chef = new Chef(this);
public Restaurant() {
executorService.execute(chef);
executorService.execute(waitPerson);
}
public static void main(String[] args) {
new Restaurant();
}
}
Restaurant是WaitPerson和Chef的焦点,他们都必须直到在为哪个Restaurant工作,因为他们必须和这家饭店的“餐窗”打交道,以便放置或拿取膳食restaurant.meal.在run中WaitPerson进入wait模式,停止其任务,直至被Chef的notifyAll唤醒。由于这是一个非常简单的程序,因此我们知道只有一个任务将在WaitPerson的锁上等待:即WaitPerson任务自身。出于这个原因,理论上可以调用notify而不是notifyAll。但是,在更复杂的情况下,可能会有多个任务在某个特定对象锁上等待,因此你不知道哪个任务应该被唤醒。因此,调用notifyAll要更安全一些,这样可以唤醒等待这个锁的所有任务,而每个任务都必须决定这个通知是否与自己相关。
一旦Chef送上Meal并通知WaitPerson,这个Chef就将等待,直至WaitPerson收集到订单并通知Chef,之后Chef就可以烧下一份Meal了。
注意,wait被包装在一个while语句中,这个语句在不断地测试正在等待的事物。乍看上去这有点怪–如果在等待一个订单,一旦你被唤醒,这个订单就必定是可以获得的,对吗?正如前面注意到的,问题是在并发应用中,某个其他的任务可能会在WaitPerson被唤醒时,会突然插足并拿走订单,唯一安全的方式是使用下面这种wait的惯用法(当然要在恰当的同步内部,并采用防止错失信号可能性的程序设计):
while(conditionIsNotMet)
wait();
这可以保证在退出等待循环之前,条件将得到满足,并且如果你收到了关于某事物的通知,而它与这个条件并无关系(就像在使用notifyAll时可能发生的情况一样),或者在你完全退出等待循环之前,这个条件发生了变化,都可以确保重返等待状态。
请注意观察,对notifyAll的调用必须首先捕获waitPerson上的锁,而在WaitPerson,run中的对wait的调用会自动地释放这个锁,因此这是有可能实现,因为调用notifyAll必然拥有这个锁,所以这可以保证两个试图在同一个对象上调用notifyAll的任务不会互相冲突。
通过把整个run方法体放到一个try语句块中,可使得这个run都被设计为可以有序的关闭。catch子句将紧挨着run方法的结束括号之前结束,因此,如果这个任务收到InterruptedException异常,它将在捕获异常之后立即结束。
注意,在Chef中,在调用shurdownNow之后,你应该直接从run返回,并且通常这就是你应该做的。但是,以这种方式执行还有一些更有趣的东西。记住,shutdownNow将向所有由ExecutorService启动的任务发送Interrupt,但是在Chef中,任务并没有在获得该interrupt之后立即关闭,因为当任务试图进入一个(可中断的)阻塞操作时,这个中断只能抛出InterruptedException。因此,你将看到首先显示了"Order up!",然后当Chef试图调用sleep时,抛出了InterruptedException。如果移除对sleep的调用,那么这个任务将回到run循环的顶部,并由于Thread.interrupted测试而退出,同时并不抛出异常·。
在前面的示例中,对于一个任务而言,只有一个单一的地点用于存放对象,从而使得另一个任务稍后可以使用这个对象。但是,在典型的生产者-消费者实现中,应使用先进先出队列来存储被生产和消费的对象。