Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Impara Semaforo e Barriera | Meccanismi di Sincronizzazione di Alto Livello
Quizzes & Challenges
Quizzes
Challenges
/
Multithreading in Java

bookSemaforo e Barriera

Nei programmi multithread, è spesso necessario controllare l'accesso alle risorse o sincronizzare l'esecuzione dei thread. Semaphore e Barrier sono meccanismi di sincronizzazione di alto livello che aiutano ad affrontare queste sfide.

Oggi esamineremo ciascuno di questi meccanismi in sequenza e ne comprenderemo le differenze. Iniziamo con Semaphore.

I Semaphore in Java sono implementati tramite la classe java.util.concurrent.Semaphore.

Costruttori

Semaphore(int permits): Costruttore che crea un semaphore con un certo numero di permessi. I permessi rappresentano il numero di accessi alla risorsa condivisa.

Main.java

Main.java

copy
1
Semaphore semaphore = new Semaphore(20);

Semaphore(int permits, boolean fair): Costruttore che garantisce una risoluzione in ordine di arrivo, servizio al primo arrivato.

Main.java

Main.java

copy
1
Semaphore semaphore = new Semaphore(20, true);

Se fair è impostato su true, il semaphore concederà i permessi in ordine first-in-first-out (FIFO), il che può aiutare a evitare la starvation. Predefinito - false.

Metodi principali

Il metodo acquire() richiede un singolo permesso. Se un permesso è disponibile, viene concesso immediatamente; altrimenti, il thread viene bloccato fino a quando un permesso non diventa disponibile. Una volta completato il task, il metodo release() viene utilizzato per rilasciare il permesso, restituendolo al semaphore. Se altri thread erano in attesa di un permesso, uno di essi verrà sbloccato.

Immagina un parcheggio con un numero limitato di posti. Il semaphore funziona come un controllore, tenendo traccia dei posti disponibili e negando l'accesso una volta che il parcheggio è pieno.

Main.java

Main.java

copy
1234567891011121314151617181920212223242526272829303132
package com.example; import java.util.concurrent.Semaphore; public class Main { private final Semaphore semaphore; public Main(int slots) { semaphore = new Semaphore(slots); } public void parkCar() { try { semaphore.acquire(); // Request a parking spot System.out.println("Car parked. Available slots: " + semaphore.availablePermits()); Thread.sleep(2000); // Simulate parking time } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { semaphore.release(); // Release the parking spot System.out.println("Car left. Available slots: " + semaphore.availablePermits()); } } public static void main(String[] args) { Main parking = new Main(3); // Parking lot with 3 spots for (int i = 0; i < 5; i++) { new Thread(parking::parkCar).start(); } } }

È inoltre possibile verificare quante autorizzazioni sono attualmente disponibili in Semaphore utilizzando il metodo int availablePermits(). È anche possibile tentare di ottenere un'autorizzazione tramite il metodo boolean tryAcquire(), che restituisce true se un'autorizzazione è stata ottenuta e false in caso contrario.

Main.java

Main.java

copy
1234567891011121314151617181920212223242526272829303132333435363738394041
package com.example; import java.util.concurrent.Semaphore; public class Main { // Define the maximum number of permits available private static final int MAX_PERMITS = 3; private static Semaphore semaphore = new Semaphore(MAX_PERMITS); public static void main(String[] args) { // Create and start 5 worker threads for (int i = 1; i <= 5; i++) { new Thread(new Worker(), "Worker-" + i).start(); } } static class Worker implements Runnable { @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " trying to acquire a permit..."); // Try to acquire a permit if (semaphore.tryAcquire()) { try { System.out.println(name + " acquired a permit! Available permits: " + semaphore.availablePermits()); Thread.sleep(1000); // Simulate work } catch (InterruptedException e) { e.printStackTrace(); } finally { // Release the permit after the work is done semaphore.release(); System.out.println(name + " released a permit. Available permits: " + semaphore.availablePermits()); } } else { // Inform if the permit could not be acquired System.out.println(name + " could not acquire a permit. Available permits: " + semaphore.availablePermits()); } } } }
Note
Nota

In altre parole, un Semaphore è utile quando è necessario fornire un accesso limitato e simultaneo a un determinato segmento di codice. L’unico svantaggio è la possibilità di un deadlock se i thread vengono bloccati nell’ordine sbagliato.

Ora, passiamo al prossimo meccanismo di sincronizzazione, ancora più semplice da utilizzare ma assolutamente prezioso per le esigenze.

CyclicBarrier

Le Barrier in Java sono rappresentate dalla classe java.util.concurrent.CyclicBarrier. I principali metodi di CyclicBarrier includono:

Costruttori di CyclicBarrier

CyclicBarrier(int parties): Costruttore che crea una barriera che blocca i thread fino all'arrivo di un certo numero di thread (parties).

Main.java

Main.java

copy
1
CyclicBarrier cyclicBarrier = new CyclicBarrier(10);

CyclicBarrier(int parties, Runnable barrierAction): Costruttore che crea una barriera con un determinato numero di partecipanti e un'azione (barrierAction) che viene eseguita quando tutti i partecipanti raggiungono la barriera.

Main.java

Main.java

copy
1234567
Runnable task = () -> { // This task will be executed when all parties have reached the barrier System.out.println("Hello))"); }; // Create a `CyclicBarrier` for 10 parties with a barrier action CyclicBarrier cyclicBarrier = new CyclicBarrier(10, task);

Metodi CyclicBarrier

Il metodo principale await() viene utilizzato come barriera e non permette al thread di proseguire finché tutti i thread non raggiungono questo metodo. Restituisce un numero di sequenza che indica l'ordine di arrivo dei partecipanti.

Main.java

Main.java

copy
1234567891011121314151617181920212223242526272829303132333435363738394041424344
package com.example; import java.util.concurrent.CyclicBarrier; public class Main { public static void main(String[] args) { // Create a `CyclicBarrier` for 3 parties with a barrier action CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All parties have reached the barrier. Barrier action executed."); }); // Create and start 3 worker threads for (int i = 1; i <= 3; i++) { new Thread(new Worker(barrier), "Worker-" + i).start(); } } static class Worker implements Runnable { private CyclicBarrier barrier; Worker(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " is working..."); try { // Simulate work Thread.sleep((int) (Math.random() * 1000)); System.out.println(name + " is waiting at the barrier."); barrier.await(); // Wait at the barrier // This code will execute after all parties have reached the barrier System.out.println(name + " has crossed the barrier."); } catch (Exception e) { e.printStackTrace(); } } } }

Può accadere che non tutti i thread raggiungano la barriera e il programma rimanga bloccato; a questo scopo si utilizza il metodo await(long timeout, TimeUnit unit), che è simile a await(), ma con timeout. Se il timeout scade prima che tutti i partecipanti arrivino, il metodo genera un'eccezione TimeoutException.

È inoltre possibile conoscere il numero di partecipanti necessari per completare la barriera tramite int getParties() e il metodo simile int getNumberWaiting(), che restituisce il numero di partecipanti attualmente in attesa alla barriera.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
package com.example; import java.util.concurrent.CyclicBarrier; public class Main { public static void main(String[] args) { // Create a `CyclicBarrier` for 3 parties with a barrier action CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All parties have reached the barrier. Barrier action executed."); }); System.out.println("Total number of parties required to complete the barrier: " + barrier.getParties()); // Create and start 3 worker threads for (int i = 1; i <= 3; i++) { new Thread(new Worker(barrier), "Worker-" + i).start(); } } static class Worker implements Runnable { private CyclicBarrier barrier; Worker(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " is working..."); try { // Simulate work Thread.sleep((int) (Math.random() * 1000)); System.out.println(name + " is waiting at the barrier."); barrier.await(); // Wait at the barrier // This code will execute after all parties have reached the barrier System.out.println(name + " has crossed the barrier."); } catch (Exception e) { e.printStackTrace(); } // Print the number of participants currently waiting at the barrier System.out.println("Number of participants currently waiting at the barrier: " + barrier.getNumberWaiting()); } } }

È inoltre possibile verificare se la barriera è stata distrutta nel caso in cui uno dei thread venga interrotto o il timeout di attesa sia scaduto utilizzando il metodo boolean isBroken(). Se la barriera è stata rotta, è possibile utilizzare il metodo void reset() che ripristina semplicemente la barriera.

Main.java

Main.java

copy
12345
// Check if the barrier is broken and reset it if necessary if (barrier.isBroken()) { System.out.println("Barrier is broken. Resetting the barrier."); barrier.reset(); }
Note
Nota

Si deve tenere presente che alcuni flussi potrebbero non raggiungere la barriera a causa di un errore o altro motivo e, di conseguenza, è evidente che la barriera non consentirà il passaggio ai flussi che stanno attualmente attendendo presso la barriera.

Tutto è chiaro?

Come possiamo migliorarlo?

Grazie per i tuoi commenti!

Sezione 3. Capitolo 3

Chieda ad AI

expand

Chieda ad AI

ChatGPT

Chieda pure quello che desidera o provi una delle domande suggerite per iniziare la nostra conversazione

Awesome!

Completion rate improved to 3.33

bookSemaforo e Barriera

Scorri per mostrare il menu

Nei programmi multithread, è spesso necessario controllare l'accesso alle risorse o sincronizzare l'esecuzione dei thread. Semaphore e Barrier sono meccanismi di sincronizzazione di alto livello che aiutano ad affrontare queste sfide.

Oggi esamineremo ciascuno di questi meccanismi in sequenza e ne comprenderemo le differenze. Iniziamo con Semaphore.

I Semaphore in Java sono implementati tramite la classe java.util.concurrent.Semaphore.

Costruttori

Semaphore(int permits): Costruttore che crea un semaphore con un certo numero di permessi. I permessi rappresentano il numero di accessi alla risorsa condivisa.

Main.java

Main.java

copy
1
Semaphore semaphore = new Semaphore(20);

Semaphore(int permits, boolean fair): Costruttore che garantisce una risoluzione in ordine di arrivo, servizio al primo arrivato.

Main.java

Main.java

copy
1
Semaphore semaphore = new Semaphore(20, true);

Se fair è impostato su true, il semaphore concederà i permessi in ordine first-in-first-out (FIFO), il che può aiutare a evitare la starvation. Predefinito - false.

Metodi principali

Il metodo acquire() richiede un singolo permesso. Se un permesso è disponibile, viene concesso immediatamente; altrimenti, il thread viene bloccato fino a quando un permesso non diventa disponibile. Una volta completato il task, il metodo release() viene utilizzato per rilasciare il permesso, restituendolo al semaphore. Se altri thread erano in attesa di un permesso, uno di essi verrà sbloccato.

Immagina un parcheggio con un numero limitato di posti. Il semaphore funziona come un controllore, tenendo traccia dei posti disponibili e negando l'accesso una volta che il parcheggio è pieno.

Main.java

Main.java

copy
1234567891011121314151617181920212223242526272829303132
package com.example; import java.util.concurrent.Semaphore; public class Main { private final Semaphore semaphore; public Main(int slots) { semaphore = new Semaphore(slots); } public void parkCar() { try { semaphore.acquire(); // Request a parking spot System.out.println("Car parked. Available slots: " + semaphore.availablePermits()); Thread.sleep(2000); // Simulate parking time } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { semaphore.release(); // Release the parking spot System.out.println("Car left. Available slots: " + semaphore.availablePermits()); } } public static void main(String[] args) { Main parking = new Main(3); // Parking lot with 3 spots for (int i = 0; i < 5; i++) { new Thread(parking::parkCar).start(); } } }

È inoltre possibile verificare quante autorizzazioni sono attualmente disponibili in Semaphore utilizzando il metodo int availablePermits(). È anche possibile tentare di ottenere un'autorizzazione tramite il metodo boolean tryAcquire(), che restituisce true se un'autorizzazione è stata ottenuta e false in caso contrario.

Main.java

Main.java

copy
1234567891011121314151617181920212223242526272829303132333435363738394041
package com.example; import java.util.concurrent.Semaphore; public class Main { // Define the maximum number of permits available private static final int MAX_PERMITS = 3; private static Semaphore semaphore = new Semaphore(MAX_PERMITS); public static void main(String[] args) { // Create and start 5 worker threads for (int i = 1; i <= 5; i++) { new Thread(new Worker(), "Worker-" + i).start(); } } static class Worker implements Runnable { @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " trying to acquire a permit..."); // Try to acquire a permit if (semaphore.tryAcquire()) { try { System.out.println(name + " acquired a permit! Available permits: " + semaphore.availablePermits()); Thread.sleep(1000); // Simulate work } catch (InterruptedException e) { e.printStackTrace(); } finally { // Release the permit after the work is done semaphore.release(); System.out.println(name + " released a permit. Available permits: " + semaphore.availablePermits()); } } else { // Inform if the permit could not be acquired System.out.println(name + " could not acquire a permit. Available permits: " + semaphore.availablePermits()); } } } }
Note
Nota

In altre parole, un Semaphore è utile quando è necessario fornire un accesso limitato e simultaneo a un determinato segmento di codice. L’unico svantaggio è la possibilità di un deadlock se i thread vengono bloccati nell’ordine sbagliato.

Ora, passiamo al prossimo meccanismo di sincronizzazione, ancora più semplice da utilizzare ma assolutamente prezioso per le esigenze.

CyclicBarrier

Le Barrier in Java sono rappresentate dalla classe java.util.concurrent.CyclicBarrier. I principali metodi di CyclicBarrier includono:

Costruttori di CyclicBarrier

CyclicBarrier(int parties): Costruttore che crea una barriera che blocca i thread fino all'arrivo di un certo numero di thread (parties).

Main.java

Main.java

copy
1
CyclicBarrier cyclicBarrier = new CyclicBarrier(10);

CyclicBarrier(int parties, Runnable barrierAction): Costruttore che crea una barriera con un determinato numero di partecipanti e un'azione (barrierAction) che viene eseguita quando tutti i partecipanti raggiungono la barriera.

Main.java

Main.java

copy
1234567
Runnable task = () -> { // This task will be executed when all parties have reached the barrier System.out.println("Hello))"); }; // Create a `CyclicBarrier` for 10 parties with a barrier action CyclicBarrier cyclicBarrier = new CyclicBarrier(10, task);

Metodi CyclicBarrier

Il metodo principale await() viene utilizzato come barriera e non permette al thread di proseguire finché tutti i thread non raggiungono questo metodo. Restituisce un numero di sequenza che indica l'ordine di arrivo dei partecipanti.

Main.java

Main.java

copy
1234567891011121314151617181920212223242526272829303132333435363738394041424344
package com.example; import java.util.concurrent.CyclicBarrier; public class Main { public static void main(String[] args) { // Create a `CyclicBarrier` for 3 parties with a barrier action CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All parties have reached the barrier. Barrier action executed."); }); // Create and start 3 worker threads for (int i = 1; i <= 3; i++) { new Thread(new Worker(barrier), "Worker-" + i).start(); } } static class Worker implements Runnable { private CyclicBarrier barrier; Worker(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " is working..."); try { // Simulate work Thread.sleep((int) (Math.random() * 1000)); System.out.println(name + " is waiting at the barrier."); barrier.await(); // Wait at the barrier // This code will execute after all parties have reached the barrier System.out.println(name + " has crossed the barrier."); } catch (Exception e) { e.printStackTrace(); } } } }

Può accadere che non tutti i thread raggiungano la barriera e il programma rimanga bloccato; a questo scopo si utilizza il metodo await(long timeout, TimeUnit unit), che è simile a await(), ma con timeout. Se il timeout scade prima che tutti i partecipanti arrivino, il metodo genera un'eccezione TimeoutException.

È inoltre possibile conoscere il numero di partecipanti necessari per completare la barriera tramite int getParties() e il metodo simile int getNumberWaiting(), che restituisce il numero di partecipanti attualmente in attesa alla barriera.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
package com.example; import java.util.concurrent.CyclicBarrier; public class Main { public static void main(String[] args) { // Create a `CyclicBarrier` for 3 parties with a barrier action CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All parties have reached the barrier. Barrier action executed."); }); System.out.println("Total number of parties required to complete the barrier: " + barrier.getParties()); // Create and start 3 worker threads for (int i = 1; i <= 3; i++) { new Thread(new Worker(barrier), "Worker-" + i).start(); } } static class Worker implements Runnable { private CyclicBarrier barrier; Worker(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " is working..."); try { // Simulate work Thread.sleep((int) (Math.random() * 1000)); System.out.println(name + " is waiting at the barrier."); barrier.await(); // Wait at the barrier // This code will execute after all parties have reached the barrier System.out.println(name + " has crossed the barrier."); } catch (Exception e) { e.printStackTrace(); } // Print the number of participants currently waiting at the barrier System.out.println("Number of participants currently waiting at the barrier: " + barrier.getNumberWaiting()); } } }

È inoltre possibile verificare se la barriera è stata distrutta nel caso in cui uno dei thread venga interrotto o il timeout di attesa sia scaduto utilizzando il metodo boolean isBroken(). Se la barriera è stata rotta, è possibile utilizzare il metodo void reset() che ripristina semplicemente la barriera.

Main.java

Main.java

copy
12345
// Check if the barrier is broken and reset it if necessary if (barrier.isBroken()) { System.out.println("Barrier is broken. Resetting the barrier."); barrier.reset(); }
Note
Nota

Si deve tenere presente che alcuni flussi potrebbero non raggiungere la barriera a causa di un errore o altro motivo e, di conseguenza, è evidente che la barriera non consentirà il passaggio ai flussi che stanno attualmente attendendo presso la barriera.

Tutto è chiaro?

Come possiamo migliorarlo?

Grazie per i tuoi commenti!

Sezione 3. Capitolo 3
some-alt