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