Печать четных и нечетных чисел с использованием 2 потоков

Печать четных и нечетных чисел с использованием 2 потоков

1. Вступление

В этом руководстве мы рассмотрим, как можно печатать четные и нечетные числа с помощью двух потоков.

Цель состоит в том, чтобы печатать числа по порядку, в то время как одна нить печатает только четные числа, а другая нить печатает только нечетные числа. Мы будем использовать концепции синхронизации потоков и межпотокового взаимодействия для решения проблемы.

2. Потоки в Java

Потоки - это легковесные процессы, которые могут выполняться одновременно. Одновременное выполнение нескольких потоков может быть хорошим с точки зрения производительности и загрузки ЦП, поскольку мы можем работать над несколькими задачами одновременно через разные потоки, работающие параллельно.

Более подробную информацию о потоках в Java можно найти в этомarticle.

In Java, we can create a thread by either extending the Thread class or by implementing the Runnable interface. В обоих случаях мы переопределяем методrun и пишем в нем реализацию потока.

Более подробную информацию о том, как использовать эти методы для создания потока, можно найти вhere.

3. Синхронизация потоков

В многопоточной среде возможно, что 2 или более потоков обращаются к одному и тому же ресурсу примерно в одно и то же время. Это может быть фатальным и привести к ошибочным результатам. Чтобы предотвратить это, нам нужно убедиться, что только один поток обращается к ресурсу в данный момент времени.

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

В Java мы можем пометить метод или блок как синхронизированный, что означает, что только один поток сможет ввести этот метод или блок в заданный момент времени.

Более подробную информацию о синхронизации потоков в Java можно найти вhere.

4. Связь между потоками

Межпотоковая связь позволяет синхронизированным потокам связываться друг с другом, используя набор методов.

Используемые методы:wait,notify, иnotifyAll,, которые унаследованы от классаObject.

Wait() causes the current thread to wait indefinitely until some other thread calls notify() or notifyAll() on the same object. Мы можем вызватьnotify() для пробуждения потоков, ожидающих доступа к монитору этого объекта.

Более подробную информацию о работе этих методов можно найти вhere.

5. Альтернативная печать нечетных и четных чисел

5.1. Используяwait() иnotify()

Мы будем использовать обсуждаемые концепции синхронизации и связи между потоками для печати нечетных и четных чисел в порядке возрастания, используя два разных потока.

In the first step, we’ll implement the Runnable interface to define the logic of both threads. В методеrun мы проверяем, четное или нечетное число.

Если число четное, мы вызываем методprintEven классаPrinter, иначе мы вызываем методprintOdd:

class TaskEvenOdd implements Runnable {
    private int max;
    private Printer print;
    private boolean isEvenNumber;

    // standard constructors

    @Override
    public void run() {
        int number = isEvenNumber ? 2 : 1;
        while (number <= max) {
            if (isEvenNumber) {
                print.printEven(number);
            } else {
                print.printOdd(number);
            }
            number += 2;
        }
    }
}

Определим классPrinter следующим образом:

class Printer {
    private volatile boolean isOdd;

    synchronized void printEven(int number) {
        while (!isOdd) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        System.out.println(Thread.currentThread().getName() + ":" + number);
        isOdd = false;
        notify();
    }

    synchronized void printOdd(int number) {
        while (isOdd) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        System.out.println(Thread.currentThread().getName() + ":" + number);
        isOdd = true;
        notify();
    }
}

In the main method, we use the defined class to create two threads. Создаем объект классаPrinter и передаем его в качестве параметра конструкторуTaskEvenOdd:

public static void main(String... args) {
    Printer print = new Printer();
    Thread t1 = new Thread(new TaskEvenOdd(print, 10, false),"Odd");
    Thread t2 = new Thread(new TaskEvenOdd(print, 10, true),"Even");
    t1.start();
    t2.start();
}

Первый поток будет нечетным, поэтому мы передаемfalse в качестве значения параметраisEvenNumber. Для второго потока вместо этого мы передаемtrue. Мы устанавливаемmaxValue равным 10 для обоих потоков, так что печатаются только числа от 1 до 10.

Затем мы запускаем оба потока, вызывая методstart(). Это вызовет методrun() обоих потоков, как определено выше, в котором мы проверяем, является ли число четным или нечетным, и распечатываем их.

Когда запускается нечетный поток, значение переменнойnumber будет равно 1. Поскольку он меньшеmaxValue и флагisEvenNumber ложен, вызываетсяprintOdd(). В этом методе мы проверяем, является ли флагisOdd истинным, и пока он истинен, мы вызываемwait(). , посколькуisOdd изначально ложно,wait() не вызывается, а значение напечатан.

We then set the value of isOdd to true, so that the odd thread goes into the wait state and call notify(), чтобы разбудить четный поток. Затем четный поток просыпается и печатает четное число, поскольку флагodd ложен. Затем он вызываетnotify(), чтобы разбудить нечетный поток.

Тот же процесс выполняется до тех пор, пока значение переменнойnumber не станет больше, чемmaxValue.

5.2. Использование семафоров

Семафор контролирует доступ к общему ресурсу с помощью счетчика. Еслиcounter is greater than zero, then access is allowed. Если это ноль, то доступ запрещен.

Java предоставляет классSemaphore в пакетеjava.util.concurrent, и мы можем использовать его для реализации объясненного механизма. Более подробную информацию о семафорах можно найти вhere.

Мы создаем два потока, нечетный поток и четный поток. Нечетный поток будет печатать нечетные числа, начиная с 1, а четный поток будет печатать четные числа, начиная с 2.

Оба потока имеют объект классаSharedPrinter. The SharedPrinter class will have two semaphores, semOdd and semEven which will have 1 and 0 permits to start with. Это гарантирует, что сначала будет напечатано нечетное число.

У нас есть два методаprintEvenNum() иprintOddNum(). : нечетный поток вызывает методprintOddNum(), а четный поток вызывает методprintEvenNum().

Чтобы напечатать нечетное число, методacquire() вызывается дляsemOdd, и, поскольку начальное разрешение равно 1, он успешно получает доступ, печатает нечетное число и вызываетrelease() дляsemEven.с

Вызовrelease() увеличит разрешение на 1 дляsemEven, и тогда четный поток может успешно получить доступ и распечатать четное число.

Это код для рабочего процесса, описанного выше:

public static void main(String[] args) {
    SharedPrinter sp = new SharedPrinter();
    Thread odd = new Thread(new Odd(sp, 10),"Odd");
    Thread even = new Thread(new Even(sp, 10),"Even");
    odd.start();
    even.start();
}
class SharedPrinter {

    private Semaphore semEven = new Semaphore(0);
    private Semaphore semOdd = new Semaphore(1);

    void printEvenNum(int num) {
        try {
            semEven.acquire();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println(Thread.currentThread().getName() + num);
        semOdd.release();
    }

    void printOddNum(int num) {
        try {
            semOdd.acquire();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println(Thread.currentThread().getName() + num);
        semEven.release();

    }
}

class Even implements Runnable {
    private SharedPrinter sp;
    private int max;

    // standard constructor

    @Override
    public void run() {
        for (int i = 2; i <= max; i = i + 2) {
            sp.printEvenNum(i);
        }
    }
}

class Odd implements Runnable {
    private SharedPrinter sp;
    private int max;

    // standard constructors
    @Override
    public void run() {
        for (int i = 1; i <= max; i = i + 2) {
            sp.printOddNum(i);
        }
    }
}

6. Заключение

В этом уроке мы рассмотрели, как мы можем печатать нечетные и четные числа поочередно, используя два потока в Java. Мы рассмотрели два метода для достижения тех же результатов:using wait() and notify() иusing a Semaphore.

И, как всегда, доступен полный рабочий кодover on GitHub.