Не могу решить задачу по wait/notify

Начал изучать wait/notify, решаю следующую задачу, которую следует решить при помощи wait/notify.

Есть следующие потоки: повар ,официантка и посетители, число посетителей задано параметром. Каждого посетителя нужно накормить обедом из 3 блюд. Повар готовит 1 блюдо, официантка берет его и относит 1 посетителю , затем (случайным образом) повар либо готовит 2 блюдо и оно относится официанткой 1 клиенту, либо повар готовит 1 блюдо для 2 клиента. В следующий раз повар уже может приготовить либо 1 блюдо для нового клиента( если остались клиенты) , либо очередное блюдо, которое еще не получал предыдущий клиент. Но если например 2 клиента ждут второе блюдо, то первым его должен получить клиент с меньшим номером. Если клиент не получил еще второе блюдо, то он не может получить третье блюдо. Выводить номер блюда вместе с именем "повар" или "официантка" или "клиент" с номером клиента в зависимости от того , кто работает в данный момент времени. Работа потоков заканчивается, когда всех посетителей обслужили.

Выбор поваром блюда для готовки немного упростил, вряд ли это сильно меняет суть задачи.

Вот мой код:

import java.util.ArrayList;
import java.util.List;

public class Main {

    // Есть следующие потоки: повар ,официантка и посетители, число посетителей задано параметром. Каждого посетителя нужно накормить обедом из 3 блюд.
    // Повар готовит 1 блюдо, официантка берет его и относит 1 посетителю , затем (случайным образом) повар либо готовит 2 блюдо и оно относится официанткой 1 клиенту,
    // либо повар готовит 1 блюдо для 2 клиента.
    // В следующий раз повар уже может приготовить либо 1 блюдо для нового клиента( если остались клиенты) , либо очередное блюдо, которое еще не получал предыдущий клиент. Но если
    // например 2 клиента ждут второе блюдо, то первым его должен получить клиент с меньшим номером. Если клиент не получил еще второе блюдо, то он не может получить третье блюдо.
    // Выводить номер блюда вместе с именем "повар" или "официантка" или "клиент" с номером клиента в зависимости от того , кто работает в данный момент времени.
    // Использовать ограничения из задания 3. Работа потоков заканчивается, когда всех посетителей обслужили

    public static void main(String[] args) {
        cafe();
    }

    public static void cafe () {
        Cafe cafe = new Cafe();
        Officiant officiant = new Officiant(cafe);
        Cook cook =  new Cook(cafe);

        List<Customer> customers = new ArrayList<>();
        cafe.customers = customers;

        for (int i = 0; i < 10; i++) {
            Customer customer = new Customer(cafe, "Customer "+i);
            customers.add(customer);
        }

        for (Customer customer : customers) {
            new Thread(customer).start();
            System.out.println(customer.name + " вошел");
        }

        new Thread(cook).start();
        new Thread(officiant).start();

    }

    static class Cafe {
        Integer currentDishType = null; // 1 or 2 or 3
        Integer currentCustomerIndex = null;
        List<Customer> customers;
        Integer lastCookedDish;

        public synchronized void eat (Customer customer) {
            try {
                while (!(currentCustomerIndex != null && currentCustomerIndex == customers.indexOf(this) && currentDishType != null)) {
                    System.out.println(customer.name+" ждет");
                    wait();
                }

                if (currentDishType == 1)
                    customer.eaten1 = true;
                else if (currentDishType == 2)
                    customer.eaten2 = true;
                else if (currentDishType == 3)
                    customer.eaten3 = true;

                System.out.println("["+customer.name+"] Текущее блюдо "+currentDishType);
                System.out.println("["+customer.name+"] Текущий клиент "+currentCustomerIndex);
                System.out.println(customer.name + " сьел блюдо " + currentDishType);

                currentDishType = null;
                currentCustomerIndex = null;

                if (customer.eaten3) {
                    customers.remove(this);
                    System.out.println(customer.name + " покинул кафе");
                }

                notify();

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        private synchronized void deliver () {
            try {
                while (currentCustomerIndex != null || currentDishType == null) {
                    System.out.println("Официант ждет");
                    wait();
                }

                int customerIndex = 0;
                for (Customer customer : customers) {
                    if (currentDishType == 1 && !customer.eaten1)
                        break;
                    else if (currentDishType == 2 && !customer.eaten2)
                        break;
                    else if (currentDishType == 3 && !customer.eaten3)
                        break;
                    customerIndex++;
                }

                currentCustomerIndex = customerIndex;
                System.out.println("[Официант] Текущее блюдо "+currentDishType);
                System.out.println("[Официант] Текущий клиент "+currentCustomerIndex);
                System.out.println(
                        "[Официант] Официант отнес блюдо " + currentDishType + " посетителю " + customers.get(currentCustomerIndex).name
                );

                notify();

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        private synchronized void cook () {
            try {
                while (currentCustomerIndex != null || currentDishType != null) {
                    System.out.println("Повар ждет");
                    wait();
                }

                int dish;
                if (lastCookedDish == null)
                    dish = 1;
                else if (lastCookedDish == 3)
                    dish = 1;
                else
                    dish = lastCookedDish + 1;
                lastCookedDish = dish;

                currentDishType = dish;

                System.out.println("[Повар] Текущее блюдо "+currentDishType);
                System.out.println("[Повар] Текущий клиент "+currentCustomerIndex);
                System.out.println("[Повар] Повар испек блюдо " + dish);

                notify();

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    static class Customer implements Runnable {

        boolean eaten1 = false;
        boolean eaten2 = false;
        boolean eaten3 = false;
        Cafe cafe;
        String name;

        Customer (Cafe cafe, String name) {
            this.cafe = cafe;
            this.name = name;
        }

        @Override
        public void run() {
            while (cafe.customers.contains(this)) {
                cafe.eat(this);
            }
            System.out.println("thread "+name+" finished");
        }
    }


    static class Officiant implements Runnable {

        Cafe cafe;

        Officiant (Cafe cafe) {
            this.cafe = cafe;
        }

        @Override
        public void run() {
            while (!cafe.customers.isEmpty()) {
                cafe.deliver();
            }
            System.out.println("thread officiant finished");
        }
    }


    static class Cook implements Runnable {

        Cafe cafe;

        public Cook(Cafe cafe) {
            this.cafe = cafe;
        }

        @Override
        public void run() {

            while (!cafe.customers.isEmpty()) {
                cafe.cook();
            }
            System.out.println("thread cook finished");
        }
    }
}

У меня не сформировалось пока четкого понимания работы wait/notify, поэтому моя ошибка может быть очевидной. Подскажите, пожалуйста, что не так.


Ответы (1 шт):

Автор решения: Roman Danilov

У вас сразу несколько проблем в коде. Как уже писали в комментариях, проблема с использованием notify() состоит в том, что данный метод пробуждает один поток, причем случайным образом. Поэтому у вас может проснуться поток, который не удовлетворит вашим условиям и снова уйдёт в wait(). Обязательно поменяйте notify() на notifyAll(), чтобы просыпались все потоки.

Следующая проблема состоит в ваших условиях, например:

while (!(currentCustomerIndex != null && currentCustomerIndex == customers.indexOf(this) && currentDishType != null)) {
    System.out.println(customer.name+" ждет");
    wait();
}

А именно в условии customers.indexOf(this) - this в данном случае у вас Cafe а не Customer, таким образом данное условие никогда не выполнится. Нужно изменить на customers.indexOf(customer).

Та же самая проблема с удалением из списка посетителей:
customers.remove(this); меняем на customers.remove(customer);

И последняя проблема с тем, что когда официант ждёт и потом просыпается, чтобы доставить блюдо, может получиться так, что к тому времени уже все посетители разойдутся. Нужно обязательно проверить, что в кафе есть посетители в тот момент, когда официант проснётся, если их нет, то продолжать разносить блюда не нужно:

while (currentCustomerIndex != null || currentDishType == null) {
    System.out.println("Официант ждет");
    wait();
}
                
if (customers.isEmpty()) {
    return;
}

После внесения изменений код будет работать, как положено.

→ Ссылка