執行緒間協作-《thinking in java》讀書筆記(一)

不鹹發表於2019-02-01

概述

執行緒間協作即有多個執行緒需要按照一定順序相互協作進行。主要有兩種方法來實現,使用鎖(互斥)來同步兩個任務的行為。另一種是使用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\n");
                      restaurant.order =null;
                      restaurant.chef.notifyAll();//告訴廚師可以做菜了
                  }
              }
          }catch (Exception e){
              e.printStackTrace();
          }
      }
    }複製程式碼

    圖片.png
    圖片.png

    由此可見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()+"\n");
                  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\n");
                  Order order =finishedQueue.take();
                  System.out.print("waiter:order complete  "+ order.toString()+"\n");
              }
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
    }複製程式碼

圖片.png
圖片.png

在這個版本中,我們沒有在任何一個地方顯示加鎖,但它仍能有序進行非常簡單

總結

我們通過兩種方法完成了執行緒的協作,個人覺得使用BlockingQueuer更容易也更好管理。最後還有一個例子模擬生產吐司麵包,第一步製作吐司,第二步抹黃油,第三步塗果醬。程式碼已同步到github,不再贅述。如發現錯誤,歡迎指正。

相關文章