Отпечатвайте четни и нечетни числа, като използвате 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 е false, се извиква printOdd () . В метода проверяваме дали знамето isOdd е вярно и докато е вярно, извикваме wait (). Тъй като isOdd е false първоначално, 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 () и използване на Semaphore .

И както винаги, пълният работен код е достъпен в GitHub.