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




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章