Задача на многопоточность: шагающий робот или пинг-понг
Published: 2023-11-25 23:18:24

Задача следующая. Нужно написать многопоточную программу на Java, в которой будет два потока, первый поток будет печатать “Ping”, второй поток “Pong”. Причем они должны делать это последовательно. Вначале первый поток печатает Ping, а потом второй Pong, потом первый Ping, потом второй Pong и т.д.

Есть вариация этой задачи про шагающий робот. Там также нужно два потока, каждый отвечает за одну из ног робота. Один поток “ходит” левой ногой, второй правой ногой. Они должны делать это последовательно. Начать можно например с левой ноги.

Вариантов решения можно придумать несколько. Можно просто использовать **wait-notify** механизм в Java.

Что нужно знать про wait — вначале нужно получить лок/монитор на объект, на котором он вызывается. Более того, рекомендуется вызов wait помещать в цикл while (condition):

```java
    //получаем монитор на объект лок
    synchronized(lock) {
      while (condition) {
        lock.wait();
      }
    }
```

Вначале получаем лок/монитор на объект, потом вызываем wait. wait переводит поток в режим ожидания. При этом он **отпускает** монитор, чтобы другой поток смог получить монитор на тот же самый объект и вызвать **notify**(), который разбудит наш поток. Когда наш поток разбудили, он попытается снова получить лок на объект. Если ему это удается, он проверит условие в цикле. Если оно изменилось, то он не выполнит wait снова и выполнение потока продолжится. Если же condition не изменилось, то он снова уйдет в сон. Дополнительный цикл нужен, чтобы удостовериться, что поток разбудили, когда действительно произошло, то, чего ждал поток. Иногда происходят спорадические пробуджения или кто-то вызвал notifyAll() и наш поток еще не должен быть разбужен. **Детально смотрите: [wait](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/Object.html#wait(long,int)).**

Что нужно знать про **notify**. Его тоже нужно вызывать после получения лока/монитора на объект, на котором мы хотим вызвать **notify**. Когда он вызван, то поток, который ждет на этом объекте будет разбужен.

**Вернемся к задаче.**

Логика работы у Левой ноги будет такая:

 1. Получаем лок

 2. Делаем ход левой ногой

 3. Меняем флаг следующего хода на правую ногу

 4. Нотифицирует ожидающий поток для правой ноги

 5. Уходим в ожидание, до момента пробуждения и когда следующий ход — ход левой ногой.

Логика работы Правой ноги:

 1. Получаем лок

 2. Ждем пока нас не разбудят и не будет следующий ход правой ногой

 3. Когда нас разбудили и ход правой ногой, делаем ход ногой

 4. Меняем флаг следующего хода на левую ногу

 5. Нотифицирует ожидающий поток для левой ноги

Пример реализации на Java:

```java
    class Leg extends Thread {
        private static boolean leftStep = true;
        private static final int numberOfSteps = 10;
        private final LegType legType;
        private final Object lock;
    
        public Leg(LegType legType, Object lock) {
            this.legType = legType;
            this.lock = lock;
        }
    
        public void run() {
            try {
                for (int i = 0; i < numberOfSteps; i++) {
                    //Получаем лок
                    synchronized (lock) {
                        //Если нога правая, то ждем пока нас не разбудят 
                        //и не наступит ход правой
                        if (legType.equals(LegType.RIGHT)) {
                            while (leftStep) {
                                lock.wait();
                            }
                        }
                        //Делаем ход ногой
                        System.out.println(legType.getTextToPrint());
                        //Меняем следующий ход на противоположный. 
                        //С левой на правую, с правой на левую
                        leftStep = !leftStep;
                        //Разбуживаем второй поток
                        lock.notifyAll();
                        //Если нога левая, то ждем пока нас не разбудят 
                        //и не наступит ход левой
                        if (legType.equals(LegType.LEFT)) {
                            while (!leftStep) {
                                lock.wait();
                            }
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public class Robot {
        public static void main(String[] args) {
            Object lock = new Object();
            Leg leftLeg = new Leg(LegType.LEFT, lock);
            Leg rightLeg = new Leg(LegType.RIGHT, lock);
            leftLeg.start();
            rightLeg.start();
        }
    }
    enum LegType {
        LEFT("Left step"),
        RIGHT("Right step");
        private final String textToPrint;
    
        LegType(String textToPrint) {
            this.textToPrint = textToPrint;
        }
    
        public String getTextToPrint() {
            return textToPrint;
        }
    }
```

Вывод программы:

    Left step
    Right step
    Left step
    Right step
    Left step
    Right step
    Left step
    Right step
    Left step
    Right step
    Left step
    Right step
    Left step
    Right step
    Left step
    Right step
    Left step
    Right step
    Left step
    Right step