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

Java 多线程:为什么我的程序以不同的结果终止?,java,multithreading,thread-safety,Java,Multithreading,Thread Safety,我正在做一些关于多线程的研究,并试图编写程序 我编写了一个餐厅程序,模拟并行服务多个客户: 一家餐馆开门,创造一名服务员、一名厨师和一些顾客,并等待所有顾客吃完他们的饭菜 一位顾客下了一道菜,等待布尔值“eated”变为真,然后通知餐厅 服务员等待客人点菜,然后通知厨师 厨师等待服务员通知他点菜,准备饭菜,并将顾客的“吃过的”设置为真 不知何故,我的程序将以大致不同的结果结束 在我做了研究之后,我可以看到两个不同终止的原因:1如果我的方法没有同步,这在我的程序中是不一样的。2因为我们不能影响线程

我正在做一些关于多线程的研究,并试图编写程序

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

一家餐馆开门,创造一名服务员、一名厨师和一些顾客,并等待所有顾客吃完他们的饭菜 一位顾客下了一道菜,等待布尔值“eated”变为真,然后通知餐厅 服务员等待客人点菜,然后通知厨师 厨师等待服务员通知他点菜,准备饭菜,并将顾客的“吃过的”设置为真 不知何故,我的程序将以大致不同的结果结束

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

这个问题并不是真正的理论问题,很明显,代码中存在一些不正确的地方

推测一下,我的猜测是厨师在等待服务员第二次通知之前没有检查现有的订单

情景:

顾客向服务员下订单 服务员通知厨师 厨师开始点菜 顾客向服务员下订单 服务员通知厨师 厨师完成第一道菜 厨师等待服务员通知 僵局
这个问题并不是真正的理论问题,很明显,代码中存在一些不正确的地方

推测一下,我的猜测是厨师在等待服务员第二次通知之前没有检查现有的订单

情景:

顾客向服务员下订单 服务员通知厨师 厨师开始点菜 顾客向服务员下订单 服务员通知厨师 厨师完成第一道菜 厨师等待服务员通知 僵局
监视器锁仅对对象的单个共享实例起作用。你的厨师和服务员的相貌不一样,因此实际上也不会相互吸引

事实上,厨师在无限期阻塞之前收到订单更像是一种侥幸

创建一个对象,可能是订单锁,服务员用它告诉支票有可用的订单

服务员会在有一个或多个订单时调用此锁上的notify,check会在此锁上等待

将其设置为publicstaticfinal,以确保两者使用相同的锁实例

更新

有一些事情我觉得很奇怪,但我们不要误会

你的厨师依靠的是一个叫做takenOrder的州旗。此标志可以由多个线程同时修改。也没有办法阻止厨师下两道菜

服务员1接受订单1 服务员1接受订单2 Chef1准备订单2???等等,什么??? 这被称为竞赛条件。正在更改预期结果order1,然后才能处理检查

实际上,您可以通过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中可用的实现之一…有关更多详细信息,请参阅

现在,方法

就个人而言,任何实体都不应该彼此直接接触。每个餐厅都应由餐厅管理

比如说。客户准备好后,应询问下一位可用的服务员。当其中一个可用时,cus 托默将向服务员点菜。侍者要么找到nextAvailableChef并向他们下订单,或者更好的是,将订单排在订单队列中


当厨师有空时,他们会得到下一个厨师并准备它。一旦准备好,它应该放在orderReady队列中,放置它的服务生或下一个可用的服务生都可以将它交付给客户…

监视器锁仅对对象的单个共享实例起作用。你的厨师和服务员的相貌不一样,因此实际上也不会相互吸引

事实上,厨师在无限期阻塞之前收到订单更像是一种侥幸

创建一个对象,可能是订单锁,服务员用它告诉支票有可用的订单

服务员会在有一个或多个订单时调用此锁上的notify,check会在此锁上等待

将其设置为publicstaticfinal,以确保两者使用相同的锁实例

更新

有一些事情我觉得很奇怪,但我们不要误会

你的厨师依靠的是一个叫做takenOrder的州旗。此标志可以由多个线程同时修改。也没有办法阻止厨师下两道菜

服务员1接受订单1 服务员1接受订单2 Chef1准备订单2???等等,什么??? 这被称为竞赛条件。正在更改预期结果order1,然后才能处理检查

实际上,您可以通过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中可用的实现之一…有关更多详细信息,请参阅

现在,方法

就个人而言,任何实体都不应该彼此直接接触。每个餐厅都应由餐厅管理

比如说。客户准备好后,应询问下一位可用的服务员。当有空的时候,顾客会把订单交给服务员。侍者要么找到nextAvailableChef并向他们下订单,或者更好的是,将订单排在订单队列中

当厨师有空时,他们会得到下一个厨师并准备它。一旦准备好,它应该放在orderReady队列中,放置它的服务员或下一个可用的服务员可以将它交付给客户…

答案是这样的吗

synchronized(this){
...
}
上述代码不正确有两个原因

没有相互排斥。每个线程都有自己的监视器/锁。您的锁可以通过它自己的线程看到。因此,这是多余的。 永远不要在线程实例上同步错误实践。在您的例子中,这是线程的实例。 还有一件事不要扩展线程,尽量避免使用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,它可能最适合您的目的

i、 e而不是共享互斥对象obj共享ArrayBlockingQueue实例。它会照顾你的 如果订单队列为空或客户队列已满,许多事情都会等待,答案是这样的

synchronized(this){
...
}
上述代码不正确有两个原因

没有相互排斥。每个线程都有自己的监视器/锁。您的锁可以通过它自己的线程看到。因此,这是多余的。 永远不要在线程实例上同步错误实践。在您的例子中,这是线程的实例。 还有一件事不要扩展线程,尽量避免使用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,它可能最适合您的目的

i、 e而不是共享互斥对象obj共享ArrayBlockingQueue实例。它会照顾你的
如果订单队列为空或客户队列已满,许多事情都会等待,如果没有代码,我们只能推测…@MrD现在发布了一些代码没有代码,我们只能推测…@MrD现在发布了一些代码我已经尝试过了,它成功了
没什么区别。我认为它甚至已经实现了,看看代码:服务员通过调用厨师的方法prepareOrder“通知”。在chef通知这个锁的方法中,他也在等待。在我看来,通知的方法很奇怪。您的prepareOrder方法基本上意味着只有第一位厨师能够准备一份订单,而这并不重要。应向第一位免费厨师下订单。这意味着您需要一个队列,在那里可以下订单,厨师可以等待,也可以下订单。你能发布完整的代码吗?因为只有部分转储就很难解决线程之间的交互。我同意这很奇怪,我刚刚开始研究这个主题:下面是完整的代码,任何考虑到这个主题的提示都会很受欢迎:我仍在查看代码,但马上-避免静态,这不是实现您想要做的事情的正确方法,相反,将餐厅的引用传递给每个元素。另外,避免停止…我知道在OOP中静态不是一个正确的属性,它实际上只关注线程,而且还有一个餐厅。停止有什么坏处?我已经试过了,没什么区别。我认为它甚至已经实现了,看看代码:服务员通过调用厨师的方法prepareOrder“通知”。在chef通知这个锁的方法中,他也在等待。在我看来,通知的方法很奇怪。您的prepareOrder方法基本上意味着只有第一位厨师能够准备一份订单,而这并不重要。应向第一位免费厨师下订单。这意味着您需要一个队列,在那里可以下订单,厨师可以等待,也可以下订单。你能发布完整的代码吗?因为只有部分转储就很难解决线程之间的交互。我同意这很奇怪,我刚刚开始研究这个主题:下面是完整的代码,任何考虑到这个主题的提示都会很受欢迎:我仍在查看代码,但马上-避免静态,这不是实现您想要做的事情的正确方法,相反,将餐厅的引用传递给每个元素。另外,避免停止…我知道在OOP中静态不是一个正确的属性,它实际上只关注线程,而且还有一个餐厅。停止有什么坏处?我做了更多的研究并改进了版本,但它仍然挂起。你可以看一下吗?全部代码:当然,给我一点时间谢谢。我的猜测是,当我期望工作线程继续工作时,一些工作线程就死了。如果是这样的话,我怎样才能让他们在仍然有一些未服务的客户的情况下重新启动运行方法呢?我已经做了更多的研究并改进了版本,但它仍然挂起。你可以看一下吗?全部代码:当然,给我一点时间谢谢。我的猜测是,当我期望工作线程继续工作时,一些工作线程就死了。如果是这样的话,当仍然有一些未服务的客户时,我如何让他们重新启动运行方法?