Java 多线程 生产者和消费者


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();
	}
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值