Перейти к основному содержимому

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

· 6 мин. чтения

1. Введение

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

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

2. Потоки в Java

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

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

В Java мы можем создать поток, либо расширив класс Thread , либо реализовав интерфейс Runnable . В обоих случаях мы переопределяем метод run и пишем в нем реализацию потока.

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

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

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

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

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

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

4. Межпоточная связь

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

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

Функция Wait() заставляет текущий поток бесконечно ждать, пока какой-либо другой поток не вызовет notify() или notifyAll() для того же объекта. Мы можем вызвать notify() для пробуждения потоков, ожидающих доступа к монитору этого объекта.

Подробнее о работе этих методов можно узнать здесь .

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

5.1. Использование wait() и notify()

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

На первом этапе мы реализуем интерфейс Runnable для определения логики обоих потоков . В методе 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();
}
}

В основном методе мы используем определенный класс для создания двух потоков. Создаем объект класса 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 равен false, вызывается printOdd() . В методе мы проверяем, является ли флаг isOdd истинным, и пока он истинен, мы вызываем wait(). Поскольку изначально isOdd имеет значение false, функция wait() не вызывается, а значение печатается.

Затем мы устанавливаем значение isOdd в true, чтобы нечетный поток переходил в состояние ожидания и вызывал notify() для пробуждения четного потока. Затем четный поток просыпается и печатает четное число, поскольку флаг нечетности равен false. Затем он вызывает notify() , чтобы разбудить нечетный поток.

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

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

Семафор управляет доступом к общему ресурсу с помощью счетчика. Если счетчик больше нуля, то доступ разрешен . Если он равен нулю, то доступ запрещен.

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

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

Оба потока имеют объект класса SharedPrinter . Класс SharedPrinter будет иметь два семафора, semOdd и semEven , которые будут иметь разрешения 1 и 0 для начала с . Это гарантирует, что нечетное число будет напечатано первым.

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

Чтобы напечатать нечетное число, для semOdd вызывается методAcquire () , и, поскольку начальное разрешение равно 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. Мы рассмотрели два метода достижения одинаковых результатов: использование wait() и notify() и использование семафора .

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