Stampa numeri pari e dispari utilizzando 2 fili

1. Introduzione

In questo tutorial, daremo un'occhiata a come possiamo stampare numeri pari e dispari usando due thread.

L'obiettivo è stampare i numeri in ordine, mentre un thread stampa solo i numeri pari e l'altro thread stampa solo i numeri dispari. Useremo i concetti di sincronizzazione dei thread e comunicazione tra thread per risolvere il problema.

2. Thread in Java

I thread sono processi leggeri che possono essere eseguiti contemporaneamente. L'esecuzione simultanea di più thread può essere utile per quanto riguarda le prestazioni e l'utilizzo della CPU poiché possiamo lavorare su più attività contemporaneamente attraverso diversi thread in esecuzione in parallelo.

Ulteriori informazioni sui thread in Java sono disponibili in questo articolo.

In Java, possiamo creare un thread estendendo la classe Thread o implementando l' interfaccia Runnable . In entrambi i casi, sovrascriviamo il metodo run e vi scriviamo l'implementazione del thread.

Ulteriori informazioni su come utilizzare questi metodi per creare un thread sono disponibili qui.

3. Sincronizzazione dei thread

In un ambiente multi-thread, è possibile che 2 o più thread accedano alla stessa risorsa all'incirca nello stesso momento. Questo può essere fatale e portare a risultati errati. Per evitare ciò, dobbiamo assicurarci che solo un thread acceda alla risorsa in un determinato momento.

Possiamo ottenere ciò utilizzando la sincronizzazione dei thread.

In Java, possiamo contrassegnare un metodo o un blocco come sincronizzato, il che significa che solo un thread sarà in grado di accedere a quel metodo o blocco in un determinato momento.

Maggiori dettagli sulla sincronizzazione dei thread in Java possono essere trovati qui.

4. Comunicazione Inter-Thread

La comunicazione tra thread consente ai thread sincronizzati di comunicare tra loro utilizzando una serie di metodi.

I metodi utilizzati sono wait , notify e notifyAll, tutti ereditati dalla classe Object .

Wait () fa sì che il thread corrente attenda indefinitamente fino a quando un altro thread non chiama notify () o notifyAll () sullo stesso oggetto. Possiamo chiamare notify () per riattivare i thread in attesa di accesso al monitor di questo oggetto.

Maggiori dettagli sul funzionamento di questi metodi possono essere trovati qui.

5. Stampa di numeri pari e dispari in alternativa

5.1. Usare wait () e notify ()

Useremo i concetti discussi di sincronizzazione e comunicazione tra thread per stampare numeri pari e dispari in ordine crescente utilizzando due thread diversi.

Nel primo passaggio, implementeremo l' interfaccia Runnable per definire la logica di entrambi i thread . Nel metodo run , controlliamo se il numero è pari o dispari.

Se il numero è pari, chiamiamo il metodo printEven della classe Printer , altrimenti chiamiamo il metodo 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; } } } 

Definiamo la classe Printer come segue:

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(); } }

Nel metodo principale, usiamo la classe definita per creare due thread. Creiamo un oggetto della classe Printer e lo passiamo come parametro al costruttore 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(); }

Il primo thread sarà il thread dispari, quindi passiamo false poiché il valore del parametro èEvenNumber . Per il secondo thread, invece , passiamo true . Abbiamo impostato il maxValue a 10 per entrambi i fili, in modo che solo i numeri da 1 a 10 vengono stampati.

Quindi avviamo entrambi i thread chiamando il metodo start () . Questo richiamerà il metodo run () di entrambi i thread come definito sopra in cui controlliamo se il numero è pari o dispari e li stampiamo.

Quando il thread dispari inizia a funzionare, il valore della variabile numero sarà 1. Poiché è minore di maxValue e il flag isEvenNumber è falso, viene chiamato printOdd () . Nel metodo, controlliamo se il flag isOdd è vero e mentre è vero chiamiamo wait (). Poiché isOdd è inizialmente false, wait () non viene chiamato e il valore viene stampato.

Quindi impostiamo il valore di isOdd su true, in modo che il thread dispari vada in stato di attesa e chiami notify () per riattivare il thread pari. Il thread pari quindi si sveglia e stampa il numero pari poiché il flag dispari è falso. Quindi chiama notify () per riattivare il thread dispari.

Lo stesso processo viene eseguito fino a quando il valore della variabile number è maggiore di maxValue .

5.2. Utilizzo dei semafori

Un semaforo controlla l'accesso a una risorsa condivisa tramite l'uso di un contatore. Se il contatore è maggiore di zero, l'accesso è consentito . Se è zero, l'accesso è negato.

Java fornisce la classe Semaphore nel pacchetto java.util.concurrent e possiamo usarla per implementare il meccanismo spiegato. Maggiori dettagli sui semafori possono essere trovati qui.

Creiamo due thread, un thread dispari e un thread pari. Il thread dispari stamperà i numeri dispari a partire da 1 e il thread pari stamperà i numeri pari a partire da 2.

Entrambi i thread hanno un oggetto della classe SharedPrinter . La classe SharedPrinter avrà due semafori, semOdd e semEven che avranno 1 e 0 permessi con cui iniziare . Ciò assicurerà che il numero dispari venga stampato per primo.

Abbiamo due metodi printEvenNum () e printOddNum (). Il thread dispari chiama il metodo printOddNum () e il thread pari chiama il metodo printEvenNum () .

Per stampare un numero dispari, il metodo acquis () viene chiamato su semOdd , e poiché il permesso iniziale è 1, acquisisce correttamente l'accesso, stampa il numero dispari e chiama release () su semEven.

La chiamata a release () incrementerà il permesso di 1 per semEven , e il thread pari può quindi acquisire con successo l'accesso e stampare il numero pari.

Questo è il codice per il flusso di lavoro descritto sopra:

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

In questo tutorial, abbiamo esaminato come stampare numeri dispari e pari alternativamente utilizzando due thread in Java. Abbiamo esaminato due metodi per ottenere gli stessi risultati: utilizzando wait () e notify () e utilizzando un semaforo .

E, come sempre, il codice funzionante completo è disponibile su GitHub.