多线程:为什么我的程序以不同的结果终止?

时间:2013-07-15 01:52:26

标签: java multithreading thread-safety

我正在研究多线程并尝试编写程序。

我写了一个餐厅程序,模拟并行服务多个客户:

  • 一家餐馆开业,创建一名服务员,一名厨师,一些顾客并等待所有顾客都吃完饭菜
  • 客户下订单,等待他的布尔'吃'成为真,然后他通知餐馆
  • 服务员等待客户下订单,然后通知厨师
  • 厨师等待服务员通知他有关订单,准备餐点并确定顾客'吃'真的

不知何故,我的程序会以大致不同的结果终止。

经过研究我已经完成了,我可以看到两种不同的终止原因: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

3 个答案:

答案 0 :(得分:2)

回答问题是这个

synchronized(this){
...
}

上述代码不正确有两个原因。

  1. 没有相互排斥。每个线程都有自己的监视器/锁。您的锁可以通过自己的线程看到。因此,synchronized(this)是多余的。
  2. 你永远不应该在Thread实例上进行同步(糟糕的做法)。在你的情况下,这是Thread的实例。 还有一件事没有扩展线程,请尽量避免使用Runnable
  3. 如何解决?

    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它可能最适合您的目的

    即代替共享mutex obj共享ArrayBlockingQueue实例。它会照顾 如果订单队列为空或客户队列已满,

    等许多类似的东西会等待

答案 1 :(得分:1)

问题不是理论上的问题,显然代码中存在一些不正确的问题。

据推测,我的猜测是Chef在等待第二次Waiter通知之前没有检查现有订单。

情景:

  1. 客户向服务员发出订单
  2. 服务员通知厨师
  3. 厨师开始下订单
  4. 客户向服务员发出订单
  5. 服务员通知厨师
  6. Chef完成第一个订单
  7. 厨师等待服务员通知
  8. 死锁。

答案 2 :(得分: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队列中,放置它的服务员或下一个可用的服务员可以将它交付给客户......