Напечатать последовательность чисел при помощи нескольких потоков на Java
Published: 2023-12-11 11:38:37

**Условие.** Необходимо написать программу на Java, которая напечатает последовательно числа от 0 до N — 1 (или от 1 до N) при помощи нескольких потоков.

**Решение.**

Для этого объявим переменную i, доступ к которой будет только после лока на одном и том же объекте для всех потоков: lock.

В synchronized блоке будем печатать и увеличивать i. Также перед печатью нужно проверить, что она еще не превысила заданное значение. Если превысила, то нужно просто выйти из функции run потока при помощи метода return.

Реализация:

```java
    class PrintSequenceThread extends Thread {
        private static Object lock = new Object();
        private static int i = 0;
        private final int max;
    
        public PrintSequenceThread(int max) {
            this.max = max;
        }
    
        public void run() {
            while (true) {
                synchronized (lock) {
                    if (i >= max) {
                        return;
                    }
                    System.out.println(i + " " + Thread.currentThread().getName());
                    i++;
                }
            }
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            int numberOfThreads = 4;
            int maxValueToPrint = 10;
            Thread[] threads = new Thread[numberOfThreads];
            for (int i = 0; i < numberOfThreads; i++) {
                threads[i] = new PrintSequenceThread(maxValueToPrint);
            }
            for (Thread t : threads) {
                t.start();
            }
        }
    }

    0 Thread-0
    1 Thread-0
    2 Thread-0
    3 Thread-0
    4 Thread-0
    5 Thread-0
    6 Thread-0
    7 Thread-0
    8 Thread-0
    9 Thread-0
```

Несмотря на то, что такая реализация работает и она Thread Safe, у нас с большой вероятностью все числа напечатает один и тот же поток. Он получит лок, напечатает, увеличит, отпустит лок. Потом, с большой вероятность, он снова получит лок и сделает тоже самое.
>  Что если мы хотим, чтобы 0 напечатал первый поток, 1 второй поток и т.д. ?

В таком случае задача очень похожа на другую задачу про [Ping Pong](https://dev.to/faangmaster/zadacha-na-mnoghopotochnost-shaghaiushchii-robot-ili-pingh-pongh-62d).

Будем использовать wait-notify механизм. Я подробно разбирал его тут: [Методы Object](https://telegra.ph/Kakie-metody-klassa-Object-vy-znaete-05-28) и [Ping Pong](https://dev.to/faangmaster/zadacha-na-mnoghopotochnost-shaghaiushchii-robot-ili-pingh-pongh-62d).

Для этого модифицируем предыдущую версию. Добавим wait на объекте lock, пока не настанет время текущим потоком печатать нужное число. Это можно проверить так:

```java
    while (i % numberOfThreads != threadIndex) {
        try {
            lock.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
```

Пока остаток от деления индекса на число потоков не равно номеру потока, мы ждем, пока работают и печатают другие потоки.

Как только наступит наша очередь, то мы напечатаем число (предварительно проверив, что оно еще не превысило максимальное), сделаем инкремент и разбудим другие потоки, чтобы они продолжили печать:

```java
    class PrintSequenceThread extends Thread {
        private static Object lock = new Object();
        private static int i = 0;
        private final int max;
        private final int numberOfThreads;
        private final int threadIndex;
    
        public PrintSequenceThread(int max, int numberOfThreads, int threadIndex) {
            this.max = max;
            this.numberOfThreads = numberOfThreads;
            this.threadIndex = threadIndex;
        }
    
        public void run() {
            while (i < max) {
                synchronized (lock) {
                    while (i % numberOfThreads != threadIndex) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (i < max) {
                        System.out.println(i + " " + Thread.currentThread().getName());
                    }
                    i++;
                    lock.notifyAll();
                }
            }
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            int numberOfThreads = 4;
            int maxValueToPrint = 10;
            Thread[] threads = new Thread[numberOfThreads];
            for (int i = 0; i < numberOfThreads; i++) {
                threads[i] = new PrintSequenceThread(maxValueToPrint, numberOfThreads, i);
            }
            for (Thread t : threads) {
                t.start();
            }
        }
    }

    0 Thread-0
    1 Thread-1
    2 Thread-2
    3 Thread-3
    4 Thread-0
    5 Thread-1
    6 Thread-2
    7 Thread-3
    8 Thread-0
    9 Thread-1
```