1. Meal類
class Meal { private final int orderNum; public Meal(int orderNum) { this.orderNum = orderNum; } public String toString() { return "Meal " + orderNum; }}
2. Chef類
class Chef implements Runnable { private Restaurant restaurant; private int count = 0; public Chef(Restaurant r) { restaurant = r; } public void run() { try { while (!Thread.interrupted()) { synchronized (this) { //這個時候可能會跳轉到別的線程 //慣用法 確保你可以重返等待狀態 while (restaurant.meal != null) //這個時候可能會跳轉到別的線程 wait(); // ... for the meal to be taken } //取消阻塞狀態 執行工作 if (++count == 10) { print("Out of food, closing"); //線上程內部 restaurant.exec.shutdownNow(); } printnb("Order up! "); //對notifyAll()的調用必須首先捕獲waitPerson上的鎖 //因為調用notifyAll()必然擁有這個鎖 //這可以保證兩個試圖在同一個對象上調用notifyAll()的任務不會互相衝突 //當執行到這裡的時候,如果需要等待鎖 那麼線程會進行切換到chef 所以還可以繼續運行下去 synchronized (restaurant.waitPerson) { //修改狀態的時候一定要擷取WaitPerson鎖,否則在修改狀態的時候如果線程發生了切換, //就會導致錯失訊號(比如先發了notifyAll而之後線程切換到wait)而造成的死結 restaurant.meal = new Meal(count); //此時無論WaitPerson是非阻塞/阻塞狀態 都可以執行 restaurant.waitPerson.notifyAll(); } //這個時候可能會跳轉到別的線程 使Chef線程處於阻塞/非阻塞狀態 TimeUnit.MILLISECONDS.sleep(100); } } catch (InterruptedException e) { print("Chef interrupted"); } }}
3. WaitPerson類
class WaitPerson implements Runnable { private Restaurant restaurant; public WaitPerson(Restaurant r) { restaurant = r; } public void run() { try { while (!Thread.interrupted()) { synchronized (this) { while (restaurant.meal == null) wait(); // ... for the chef to produce a meal } print("Waitperson got " + restaurant.meal); synchronized (restaurant.chef) { restaurant.meal = null; restaurant.chef.notifyAll(); // Ready for another } } } catch (InterruptedException e) { print("WaitPerson interrupted"); } }}
4. Restaurant類
//Restaurant是WaitPerson和Chef的焦點,他們都必須知道在為哪個Restaurant工作,//因為他們必須和這家飯店的“餐窗”打交道,以便放置或拿取膳食restaurant.meal .public class Restaurant { //餐窗的meal 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(); }}
類圖
執行過程:
啟動Chef線程後,
首先擷取自身鎖。此時線程調度器可能會跳到另外的線程 這些在使用synchronized同步代碼塊的時候都要考慮
(可能處於擷取到Chef鎖的狀態,也可能處於掛起後釋放Chef鎖的狀態)
while (!Thread.interrupted()) { //先擷取Chef對象鎖 //判斷是否要掛起本線程 //掛起線程 形成有一個任務在Chef的鎖上等待的狀況 //不論是否掛起此線程 都釋放此線程對象鎖 synchronized (this) { ////這個時候可能會跳轉到別的線程 //慣用法 確保你可以重返等待狀態 while (restaurant.meal != null) ////這個時候可能會跳轉到別的線程 wait(); // ... for the meal to be taken }
1. 當處於擷取Chef鎖的狀態時,線程調度器執行WaitPerson線程
由於meal==null,所以WaitPerson線程會掛起,釋放WaitPerson鎖
2. 當處於釋放Chef鎖的狀態時,線程調度器執行WaitPerson線程
由於meal==null,所以WaitPerson線程會掛起,釋放WaitPerson鎖
所以殊途同歸,還是要回到Chef線程,繼續執行
執行doMeal動作之後
先擷取WaitPerson鎖,WaitPerson鎖存不存在鎖住的情況呢。存在。比如當執行WaitPerson線程時,線程調度器在剛剛進入synchronized(this)同步代碼塊的時候切換線程,此時WaitPerson鎖就被WaitPerson線程擷取著。那麼此時Chef線程就被阻塞了,線程調度器會切換線程,WaitPerson線程繼續執行,鎖會被釋放掉,線程掛起。Chef線程可以繼續執行下去。擷取WaitPerson鎖,修改狀態(修改狀態的時候一定要擷取WaitPerson鎖,否則在修改狀態的時候如果線程發生了切換,就會導致錯失訊號(比如先發了notifyAll而之後線程切換到wait)而造成的死結)。通知WaitPerson線程,釋放WaitPerson鎖。此時WaitPerson無論處於非阻塞還是掛起狀態都可以繼續運行,而WaitPerson此時無論切換還是不切換,是掛起還是非阻塞狀態下切換都可以。
通過這個程式的閱讀,就會發現其思路根本不是那麼清晰。之前做過WaxOnWaxOff代碼的閱讀,這兩個程式在思路上有什麼區別呢。
1. WaxOnWaxOff程式 是在一個對象car上反覆執行兩個線程的操作,使用的是car對象鎖。而ChefWaitPerson程式是一個單個的生產者消費者任務,其使用了兩個線程對象鎖,更為複雜。
2. 注意在ChefWaitPerson程式中其線程調度器可以在程式的任何語句處進行線程切換(Synchronized關鍵字不能阻止線程切換,而只能保證其擷取鎖從而多任務順序執行),所以在多種情況下切換仍能保證兩個線程不發生死結的情況,其狀態管理沒有WaxOnWaxOff簡單清晰。