概述
執行緒間協作即有多個執行緒需要按照一定順序相互協作進行。主要有兩種方法來實現,使用鎖(互斥)來同步兩個任務的行為。另一種是使用BlockingQueue,它已經幫我們處理好了同步機制,實現更加簡單。
舉例
接下來以一個實際場景為例,進行演示。假設在一個餐館中有一個服務員,有一個廚師,而服務員要等到廚子把菜做好了才能上菜,然後回來繼續等待。而廚師得到新訂單後開始做菜。用兩種方式實現之前,我們分析知廚師和服務員分別是一個獨立的執行緒,他們通過餐廳聯結在一起。在這個模型中廚師代表生產者,服務員代表消費者。Order是他們共享的資源,需要進行同步。
使用鎖的互斥
-
菜
public class Order { private int num=0; public Order(int num) { this.num=num; } @Override public String toString() { return "order num:"+num; } }複製程式碼
-
餐館
public class Restaurant { Order order; Chef chef=new Chef(this); Waiter waiter=new Waiter(this); ExecutorService executorService= Executors.newCachedThreadPool(); public Restaurant() { order =null; executorService.execute(chef); executorService.execute(waiter); try { TimeUnit.SECONDS.sleep(5); }catch (Exception e){ e.printStackTrace(); } executorService.shutdown(); } public static void main(String[] args){ new Restaurant(); } }複製程式碼
- 廚師
public class Chef implements Runnable { private Restaurant restaurant; private int counter=0; public Chef(Restaurant restaurant) { this.restaurant = restaurant; } @Override public void run() { try{ while (!Thread.interrupted()){ synchronized (this){ while (restaurant.meal!=null){ wait();//等服務員上菜,獲得新訂單 } } synchronized (restaurant.waiter){ //獲得服務員的鎖,讓他等我做菜 restaurant.meal=new Meal(counter++); System.out.print("a meal is done"); Thread.sleep(500); restaurant.waiter.notifyAll(); //告訴服務員可以上菜了 } } }catch (Exception e){ e.printStackTrace(); } } }複製程式碼
-
服務員
public class Waiter implements Runnable { private Restaurant restaurant; public Waiter(Restaurant restaurant) { this.restaurant = restaurant; } @Override public void run() { try { while (!Thread.interrupted()){ synchronized (this){ while (restaurant.order ==null){ wait();//等待廚師做完菜後被chef的notifyAll()喚醒,注意wait()會釋放當前獲得的鎖 } } synchronized (restaurant.chef){ System.out.print("waiter: order up "); restaurant.order =null; restaurant.chef.notifyAll();//告訴廚師可以做菜了 } } }catch (Exception e){ e.printStackTrace(); } } }複製程式碼
由此可見chef與waiter按照順序協調了
使用BlockingQueue同步
- 建立自己的BlockingQueue
public class MealQueue extends LinkedBlockingQueue<Order> { }複製程式碼
-
餐館
public class Restaurant { private MealQueue waitQueue; private MealQueue finishedQueue; private Chef chef; private Waiter waiter; public Restaurant() { waitQueue = new MealQueue(); finishedQueue = new MealQueue(); chef = new Chef(waitQueue, finishedQueue); waiter = new Waiter(waitQueue, finishedQueue); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(chef); executorService.execute(waiter); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } executorService.shutdown(); } public static void main(String[] args){ new Restaurant(); } }複製程式碼
-
廚師
public class Chef implements Runnable{ private MealQueue waitQueue; private MealQueue finishedQueue; public Chef(MealQueue waitQueue, MealQueue finishedQueue) { this.waitQueue = waitQueue; this.finishedQueue = finishedQueue; } @Override public void run() { try { while (!Thread.interrupted()){ Order order =waitQueue.take(); Thread.sleep(500); System.out.print("chef:order done "+ order.toString()+" "); finishedQueue.add(order); } } catch (Exception e) { e.printStackTrace(); } } }複製程式碼
-
服務員
public class Waiter implements Runnable{ private MealQueue waitQueue; private MealQueue finishedQueue; private int count; public Waiter(MealQueue waitQueue, MealQueue finishedQueue) { this.waitQueue = waitQueue; this.finishedQueue = finishedQueue; count=0; } @Override public void run() { try { while (!Thread.interrupted()){ Order newOrder=new Order(count++); waitQueue.add(newOrder); System.out.print("waiter:a new order "); Order order =finishedQueue.take(); System.out.print("waiter:order complete "+ order.toString()+" "); } } catch (Exception e) { e.printStackTrace(); } } }複製程式碼
在這個版本中,我們沒有在任何一個地方顯示加鎖,但它仍能有序進行非常簡單
總結
我們通過兩種方法完成了執行緒的協作,個人覺得使用BlockingQueuer更容易也更好管理。最後還有一個例子模擬生產吐司麵包,第一步製作吐司,第二步抹黃油,第三步塗果醬。程式碼已同步到github,不再贅述。如發現錯誤,歡迎指正。