import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 生产者和消费者
*
* 请考虑这样一个饭店,它有一个厨师和一个服务员。这个服务员必须等待厨师准备好膳食。当厨师准备好时,他会通知服务员,
* 之后服务员上菜,然后返回继续等待。这是一个任务协作的示例:厨师代表生产者,而服务员代表消费者。两个任务必须在膳食被
* 生产和消费时进行握手,而系统必须以有序的方式关闭。下面是对这个叙述建模的代码:
*
* @create @author Henry @date 2016-12-21
*/
class Meal {
private final int orderNum;
public Meal(int orderNum) {
this.orderNum = orderNum;
}
@Override
public String toString() {
return "Meal " + orderNum;
}
}
/**
* 等待Meal的人,如果没有Meal就等待,直到有Meal然后拿走,通知厨师。
*
* @create @author Henry @date 2016-12-22
*/
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();// ... for the chef to produce a meal
}
System.out.println("Waitperson got " + restaurant.meal);
synchronized (restaurant.chef) {
restaurant.meal = null;
restaurant.chef.notifyAll();//Ready for another
}
}
} catch (InterruptedException e) {
System.out.println("WaitPerson interrupted");
}
}
}
/**
* 厨师类负责做饭,当有Meal就等待,没有就做一个Meal,然后通知WaitPerson。
*
* @create @author Henry @date 2016-12-22
*/
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(); // ...for the meal to be taken
}
if (++count == 10) {
System.out.println("Out of food. closing");
restaurant.exec.shutdownNow();
}
System.out.println("Order up! ");
synchronized (restaurant.waitPerson) {
restaurant.meal = new Meal(count);
restaurant.waitPerson.notifyAll();
}
TimeUnit.MILLISECONDS.sleep(100);
}
} catch (InterruptedException e) {
System.out.println("Chef interrupted");
}
}
}
/**
* Restaurant是WaitPerson和Chef的焦点,他们都必须知道在为哪个Restaurant工作,因为他们必须和这家
* 饭店的“餐窗”打交道,以便放置或拿去膳食restaurant.meal。在run()中,WaitPerson进入wait()模式,
* 停止其任务,直至被Chef的notifyAll()唤醒。由于这是一个非常简单的程序,因此我们只知道一个任务将在
* WaitPerson的锁上等待:即WaitPerson任务自身。出于这个原因,理论上可以调用notify()而不是notifyAll()。
* 但是,在更复杂的情况下,可能会有多个任务在某个特定对象锁上等待,因此你不知道哪个任务应该被唤醒。因此,
* 调用notifyAll()要更安全一些,这样可以唤醒等待这个说的 所有任务,而每个任务都必须决定这个通知是否与自己相关。
*
* 一旦Chef送上Meal并通知WaitPerson,这个Chef就将等待,直至WaitPerson收集到订单并通知Chef,之后Chef就可以烧
* 下一份Meal了。
*
* @create @author Henry @date 2016-12-22
*/
public class Restaurant {
Meal meal;
ExecutorService exec = Executors.newCachedThreadPool();
WaitPerson waitPerson = new WaitPerson(this);
Chef chef = new Chef(this);
public Restaurant() {
exec.execute(chef);
exec.execute(waitPerson);
}
public static void main(String[] args) {
new Restaurant();
}
}
注意,wait()被包装在一个while()语句中,这个语句在不断地测试正在等待的事物。乍看上去这有点怪---如果在等待一个订单,一旦你被唤醒,这个订单就必定是可获得的,对吗?正如前注意到的,问题是在并发应用中,某个其他的任务可能会在WaitPerson被唤醒时,会突然插足并拿走订单,唯一安全的方式是使用下面这种wait()的惯用法(当然要在恰当的同步内部,并采用防止错失信号可能性的程序设计):
while(conditionIsNotMet) wait();
这可以保证在你退出等待循环之前,条件将得到满足,并且如果你收到了关于某事物的通知,而它与这个条件并无关系(就象在使用notifyAll()时可能发生的情况一样),或者在你完全退出等待循环之前,这个条件发生了变化,都可以确保你可以重返等待状态。
请注意观察,对notifyAll()的调用必须首先捕获waitPerson上的锁,而在WaitPerson.run()中的对wait()的调用会自动地释放这个锁,因此这是有可能实现的。因为调用notifyAll()必然拥有这个锁,所以这可以保证两个试图在同一个对象上调用notifyAll的任务不会互相冲突。
通过把整个run()方法体放入一个try语句块中,可使得这两个run()方法都被设计为可以有序地关闭。catch子句将紧挨着run()方法的结束括号之前结束,因此,如果这个任务收到了InterruptedException异常,它将在捕获异常之后立即结束。
注意,在Chef中,在调用shutdownNow()之后,你应该直接从run()返回,并且通常这就是你应该做的。但是,以这种方式执行还有一些更有趣的东西。记住,shutdownNow()将向所有由ExecutorService启动的任务发送interrupt(),但是在Chef中,任务并没有在获得该interrupt()之后立即关闭,因为当任务试图进入一个(可中断的)阻塞操作时,这个中断只能抛出InterruptedException。因此,你将看到首先显示了“Order up!”,然后当Chef试图调用sleep()时,抛出了InterruptedException。如果移除对sleep()的调用,那么这个任务将回到run()循环的顶部,并由于Thread.interrupted()测试而退出,同时并不抛出异常。
在前面的示例中,对于一个任务而言,只有一个单一的地点用于存放对象,从而使得另一个任务稍后可以使用这个对象。你将在本章稍后学习有关这种队列知识。
使用显示的Lock和Condition对象
在Java SE5的java.util.concurrent类库中还有额外的显式工具可以用来重写WaxOMatic.java。使用互斥并允许任务挂起的基本类是Condition,你可以通过在Condition上调用await()来挂起一个任务,从而唤醒一个任务,或者调用signalAll()来唤醒所有在这个Condition上被其自身挂起的任务(与使用notifyAll()相比,signalAll()是更安全的方式)。
下面是WaxOMatic.java的重写版本,它包含一个Condition,用来在waitForWaxing()或waitForBuffering()内部挂起一个任务:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 在Car的构造器中,单个的Lock将产生一个Condition对象,这个对象被用来管理任务间的通信。但是,这个Condition
* 对象不包含任何有关处理状态的信息,因此你需要管理额外的表示处理状态的信息,即boolean waxOn。
*
* 每个对lock()的调用都必须紧跟一个try-finally子句,用来保证在所有情况下都可以释放锁。在使用内建版本时,
* 任务在可以调用await()、signal()或signalAll()之前,必须拥有这个锁。
*
* 注意,这个解决方案比前一个更加复杂,在本例中这种复杂性并未使你收获更多。Lock和Condition对象只有在更加困难的
* 多线程问题中才是必须的。
*
* @create @author Henry @date 2016-12-22
*/
class Car {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private boolean waxOn = false;
public void waxed() {
lock.lock();
try {
waxOn = true;// Ready to buff
condition.signalAll();
} finally {
lock.unlock();
}
}
public void buffed() {
lock.lock();
try {
waxOn = false;// Ready for another coat of wax
condition.signalAll();
} finally {
lock.unlock();
}
}
public void waitForWaxing() throws InterruptedException {
lock.lock();
try {
while (waxOn == false)
condition.await();
} finally {
lock.unlock();
}
}
public void waitForBuffing() throws InterruptedException {
lock.lock();
try {
while (waxOn == true)
condition.await();
} finally {
lock.unlock();
}
}
}
/**
* WaxOn.run()表示给汽车打蜡过程的第一个步骤,因此它将执行它的操作:调用sleep()以模拟需要涂蜡的
* 时间,然后告知汽车涂蜡结束,并调用waitForBuffing(),这个方法会用一个wait()调用来挂起这个任务,
* 直至WaxOff任务调用这辆汽车的buffed(),从而改变状态并调用notifyAll()为止。
*
* @create @author Henry @date 2016-12-06
*/
class WaxOn implements Runnable {
private Car car;
public WaxOn(Car c) {
this.car = c;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
System.out.print(" Wax On! ");
TimeUnit.MICROSECONDS.sleep(200);
car.waxed();
car.waitForBuffing();
}
} catch (InterruptedException e) {
System.err.println("\nExiting Wax On via interrupt");
}
System.out.println("\nEnding Wax On task");
}
}
/**
* WaxOff.run()立即进入waitForWaxing(),并以此而被挂起,直至WaxOn涂完蜡并且waxed()被调用。在运行这个程序时,
* 你可以看到当控制权在这两个任务之间来回互相传递时,这个两步骤过程在不断地重复
*
* @create @author Henry @date 2016-12-06
*/
class WaxOff implements Runnable {
private Car car;
public WaxOff(Car c) {
car = c;
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
car.waitForWaxing();
System.out.print(" Wax Off! ");
TimeUnit.MILLISECONDS.sleep(200);
car.buffed();
}
} catch (InterruptedException e) {
System.err.println("\nExiting Wax Off via interrupt");
}
System.out.println("\nEnding Wax Off task");
}
}
public class WaxOMatic2 {
public static void main(String[] args) throws Exception {
Car car = new Car();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new WaxOff(car));
exec.execute(new WaxOn(car));
TimeUnit.SECONDS.sleep(5);
exec.shutdownNow();
}
}