Друкуйте парні та непарні числа, використовуючи 2 нитки

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, щоб визначити логіку обох потоків . У методі запуску ми перевіряємо, чи є число парним чи непарним.

Якщо число парне, ми викликаємо метод 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, оскільки значення параметра - EvenNumber . Для другого потоку замість цього передаємо true . Ми встановлюємо maxValue на 10 для обох потоків, так що друкуються лише цифри від 1 до 10.

Потім ми запускаємо обидва потоки, викликаючи метод start () . Це викличе метод run () обох потоків, як визначено вище, де ми перевіряємо, непарне чи непарне число, і друкуємо їх.

Коли непарний потік почне працювати, значення змінної номер буде дорівнювати 1. Оскільки воно менше maxValue, а прапор isEvenNumber хибний, викликається printOdd () . У методі ми перевіряємо, чи є прапор isOdd істинним, і хоча це істинно, ми викликаємо wait (). Оскільки isOdd хибне спочатку, wait () не викликається, і значення друкується.

Потім ми встановлюємо значення isOdd на true, так що непарна нитка переходить у стан очікування і викликає notify (), щоб пробудити парний потік. Потім парний потік прокидається і друкує парне число, оскільки непарний прапор хибний. Потім він викликає notify () для пробудження непарної нитки.

Той самий процес виконується до тих пір, поки значення змінної номер не перевищить значення maxValue .

5.2. Використання семафорів

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

Java надає клас Semaphore у пакеті java.util.concurrent, і ми можемо використовувати його для реалізації поясненого механізму. Детальніше про семафори можна дізнатися тут.

Ми створюємо дві нитки, непарну нитку і парну нитку. Непарна нитка друкувала непарні числа, починаючи з 1, а парна нитка друкувала парні числа, починаючи з 2.

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

We have two methods printEvenNum() and printOddNum(). The odd thread calls the printOddNum() method and the even thread calls the printEvenNum() method.

To print an odd number, the acquire() method is called on semOdd, and since the initial permit is 1, it acquires the access successfully, prints the odd number and calls release() on semEven.

Calling release() will increment the permit by 1 for semEven, and the even thread can then successfully acquire the access and print the even number.

This is the code for the workflow described above:

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. Conclusion

У цьому підручнику ми розглянули, як ми можемо друкувати непарні та парні числа, використовуючи два потоки в Java. Ми розглянули два методи досягнення однакових результатів: за допомогою wait () та notify () та використання семафору .

І, як завжди, повний робочий код доступний на GitHub.