2013-07-15 24 views
2

我正在做一些關於多線程的研究,並試圖編寫程序。多線程:爲什麼我的程序以不同的結果終止?

我寫了一個餐廳的程序,模擬並行服務多個客戶:

  • 餐廳打開,創建一個服務員,廚師,一些客戶和等待,直到所有的客戶都吃過他們的飯食
  • 一位顧客下訂單,並等待他的布爾'吃'成爲真實,然後他通知餐廳
  • 服務員等待客戶做出訂單,然後通知廚師
  • 廚師等待服務員通知他關於o準備膳食並將顧客的「食用」設爲真

不知何故,我的程序將以大致不同的結果終止。

經過研究我已經完成了,我可以看到兩個終止不同的原因:1)如果我的方法不同步(在我的程序中不是這種情況)。 2),因爲我們不能影響線程資源的分配方式,但是這會導致線程序列中的一些細微差異。但是我的程序終止時存在很大的差異,而不僅僅是線程序列中的小差異:

  • 如果有一個客戶,它總是終止正確
  • 如果有多個客戶,有時一切都正確,餐廳關閉。但有時會在服務員發出第二次通知後卡住,此時廚師應該接收下一份訂單。它不會終止,線程正在運行,但廚師只是不處理下一個訂單。

有人可以給我任何提示嗎?對於廚師

代碼:

class Chef extends Thread{ 
    private static int _id=1; 
    private int id; 
    Order order; 

    public Chef(){ 
     this.id=_id; 
     _id++; 
     order=null; 
     this.start(); 
    } 

    @Override 
    public void run() { 
     System.out.println("Chef ("+id+") starts to work..."); 

      synchronized(this){ 
       while(order==null){ 
        try { 
         this.wait(); 
        } catch (InterruptedException e) { 
         e.printStackTrace(); 
        } 
       } 
      } 

      System.out.println("Chef ("+id+") prepared Order ("+this.order.getId()+")"); 

      Restaurant.customers.get(this.order.getId()-1).served=true; 
      synchronized(Restaurant.customers.get(this.order.getId()-1)){ 
       Restaurant.customers.get(this.order.getId()-1).notify(); 
      } 
        order=null; 
    } 

    public void prepareOrder(Order order){ 

     this.order=order; 
     System.out.println("Chef ("+this.id+") prepares order ("+order.getId()+")"); 
     synchronized(this){ 
      this.notify(); 
     } 
    } 
} 

的服務員碼(正常工作,一直進行收到的訂單):

class Waiter extends Thread{ 

    private static int _id=1; 
    private int id; 
    Order order; 

    public Waiter(){ 
     this.id=_id; 
     _id++; 
     order=null; 
     this.start(); 
    } 

    @Override 
    public void run() { 
     System.out.println("Waiter ("+this.id+") starts to work..."); 

     synchronized(this){ 
      while(takenOrder==false){ 
       try { 
        wait(); 
       } catch (InterruptedException e) { 
        // TODO Auto-generated catch block 
        e.printStackTrace(); 
       } 
      } 
     } 
     order=null; 

     Restaurant.chefs.get(0).prepareOrder(order); 
    } 

    public void takeOrder(Order order){ 

     this.order=order; 
     System.out.println("Waiter ("+this.id+") takes order ("+this.order.getId()+")"); 
     synchronized(this){ 
      this.notify(); 
     } 
    } 
} 

whole code

+2

沒有代碼,我們只能推測... –

+0

@MrD發佈了一些代碼,現在 – IAM

回答

2

回答的問題是這樣

synchronized(this){ 
... 
} 

以上代碼不正確的原因有兩個。

  1. 沒有互斥。每個線程都有自己的監視器/鎖。你的鎖可以通過自己的線程看到。因此同步(這)是多餘的。
  2. 你永遠不應該在Thread實例上同步(壞習慣)。在你的情況下,這是Thread的實例。 一件事不延長線,儘量避免使用Runnable接口,而不是

如何解決?

class Chef implments Runnable { 

    private Object lock; 
    Chef(Object lock) { 
    this.lock = lock; 
    } 

    public run() { 

     synchronized(lock) { 
     // do stuff here 
     } 
    } 

} 


class Waiter implments Runnable { 

    private Object lock; 
    Chef(Object lock) { 
    this.lock = lock; 
    } 

    public run() { 

     synchronized(lock) { 
     // do stuff here 
     } 
    } 

} 


//your main 

public static void main(String []args) { 
    Object obj = new Object(); 
    Thread chef = new Thread(new Chef(obj)); 
    Thread waiter = new Thread(new Waiter(obj)); 
    chef.start(); 
    waiter.start(); 
} 

上述方法建議是兩個線程之間互斥的一個非常基本的例子。 但它不是一個最好的方法。嘗試使用BlockingQueue它可能最適合您的目的

即不是共享互斥量obj共享ArrayBlockingQueue實例。它會照顧 很多事情喜歡也就會等待,如果命令隊列爲空或客戶隊列已滿

+0

非常有幫助,謝謝 – IAM

+0

我已經做了更多的研究並改進了版本,但它仍然存在。你可以看看嗎?整個代碼:http://codeviewer.org/view/code:352f – IAM

+0

肯定給我一點時間 – veritas

1

的問題是沒有真正的理論,顯然有有些代碼不正確。

據推測,我的猜測是Chef沒有檢查現有的訂單,等待Waiter第二次通知。

場景:

  1. 客戶的問題,責令服務員
  2. 服務員會通知廚師
  3. 廚師開始作出命令
  4. 廚師完成第一
  5. 客戶的問題,責令服務員
  6. 服務員會通知廚師訂單
  7. 廚師等待notif由服務員ied
  8. 僵局。
+0

我已經發布的代碼現在 – IAM

1

監視鎖只能在Object的單個共享實例上工作。你的廚師和服務員並沒有使用相同的外觀,因此實際上並沒有相互配合。

實際上,廚師在無限期阻止之前獲得訂單更像是一種僥倖。

創建一個單獨的對象(可能是ORDER_LOCK),服務員用這個對象告訴檢查有訂單可用。

服務員在該鎖具有一個或多個訂單時會調用notify,並在此鎖上檢查wait

讓它public static final所以這兩個是確保使用鎖

更新

有幾件事情我覺得怪異的同一個實例,但是讓我們不要迷路...

您的廚師依靠稱爲takenOrder的單一州旗。這個標誌可以被多個線程同時修改。也沒有辦法阻止廚師被給予兩份訂單。

即。

  • 服務員(1)採用順序(1)
  • 服務員(1)採用順序(2)
  • 廚師(1)準備順序(2)... ???等等,什麼?

這被稱爲競賽條件。預期結果(訂單(1))在檢查可以處理之前正在更改。

你可以真正看到這與你的ID代。我能夠使用相同的ID發出兩個訂單

您真正需要的是某種類型的隊列系統,其中自定義無法下訂單直到服務員可以接受爲止。

服務員可能在(服務員)隊列中,接受訂單並下達訂單。

廚師可以在(廚師)隊列或準備訂單。

客戶實際上並不在意。他們將作出決定(對於訂單),等待服務員下訂單,等待訂單,吃飯或離開。

對象只能在隊列中,如果它什麼都不做。所以它會讓隊列開始工作,一旦完成就返回。

訂單和客戶之間也沒有關係......所以您如何知道哪個訂單屬於哪個客戶?

現在,根據什麼是你想要實現的,你可以創建自己的阻塞隊列,像...

private List<Waiter> waiters; 

//...// 

public Waiter getNextAvailableWaiter() { 

    Waiter waiter = null; 

    synchronized (WAITER_QUEUE_LOCK) { 

     while (waiters.isEmpty()) { 
      WAITER_QUEUE_LOCK.wait(); 
     } 

     waiter = waiters.remove(0); 

    } 

    return waiter; 

} 

或者用在JDK中可用的實現方式之一......見BlockingQueue更多細節。

現在,方法;)

就個人而言,沒有一個實體的應有直接給對方。每個應由餐廳管理。

例如。當客戶準備好時,它應該要求nextAvailableWaiter。當一個人變得可用時,顧客將把訂單交給服務員。服務員要麼得到nextAvailableChef並給他們訂單,或者甚至更好,將訂單放在訂單隊列中。

當一名廚師可以使用時,他們將獲得nextOrder並做好準備。一旦準備好,就應該放在orderReady隊列,其中要麼把它或下一個可用服務員可以將其交付給客戶服務員...

+0

我試過了,它沒有區別。我認爲它甚至已經實施了,請看代碼:服務員通過調用廚師的方法prepareOrder來「通知」。從廚師內部通知這個鎖,他也在等待。 – IAM

+0

在我看來,通知方法很奇怪。你的'prepareOrder'方法基本上意味着只有第一個廚師才能夠準備一個不需要關心的訂單。第一位免費廚師應該接受訂單。這表明你需要一個可以下訂單的隊列,廚師可以等待或下一個訂單。您是否能夠發佈整個代碼,因爲很難僅通過部分轉儲來解決線程間的交互問題。 – MadProgrammer

+0

我同意這很奇怪,我剛剛開始研究這個話題:)所以這裏是整個代碼,任何提示考慮這個話題將非常感激:http://codeviewer.org/view/code:352a – IAM

相關問題