2

我正在对多线程进行一些研究并尝试编写程序。

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

  • 一家餐厅开张,创建一个服务员、一个厨师、一些顾客,然后等到所有顾客都吃完饭
  • 客户下订单,并等待他的布尔“吃”变为真,然后通知餐厅
  • 服务员等待客户点菜,然后通知厨师
  • 厨师等待服务员通知他订单,准备餐点并将客户的“已吃”设置为真

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

在我完成研究之后,我可以看到 2 个不同终止的原因: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();
        }
    }
}

整个代码

4

3 回答 3

2

答案问题是这个

synchronized(this){
...
}

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

  1. 不存在互斥。每个线程都有自己的监视器/锁。你的锁可以被它自己的线程看到。因此 synchronized(this) 是多余的。
  2. 您永远不应该在 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 它可能最适合您的目的

即,而不是共享互斥对象共享 ArrayBlockingQueue 实例。它会处理很多事情,例如如果订单队列为空或客户队列已满,它将等待

于 2013-07-15T03:25:40.477 回答
1

这个问题并不是真正的理论问题,显然代码有问题。

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

设想:

  1. 客户向服务员下订单
  2. 服务员通知厨师
  3. 厨师开始点菜
  4. 客户向服务员下订单
  5. 服务员通知厨师
  6. 厨师完成第一单
  7. 厨师等待服务员通知
  8. 僵局。
于 2013-07-15T01:59:30.513 回答
1

监视器锁仅适用于Object. 您的厨师和服务员没有使用相同的外观,因此实际上并没有相互配合

实际上,厨师在无限期阻止之前得到订单实际上更像是一种侥幸。

创建一个对象(可能是ORDER_LOCK),服务员用它来告诉支票有可用的订单。

当服务员有一个或多个订单时,服务员会调用notify这个锁并检查wait这个锁。

确保public static final两者使用相同的锁实例

更新

有几件事我觉得很奇怪,但不要迷路...

您的厨师依赖于一个名为takenOrder. 这个标志可以被多个线程同时修改。也没有办法阻止厨师得到两个订单。

IE。

  • 服务员(1) 点菜(1)
  • 服务员(1) 点菜(2)
  • 厨师(1) 准备订单(2) ... ??? 等等,什么???

这被称为竞争条件。预期结果 (order(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队列中,放置它的服务员或下一个可用的服务员可以将它交付给客户......

于 2013-07-15T02:15:31.453 回答