Sémaphore et Barrière
Dans les programmes multithreadés, il est souvent nécessaire de contrôler l'accès aux ressources ou de synchroniser l'exécution des threads. Semaphore et Barrier sont des mécanismes de synchronisation de haut niveau qui permettent de relever ces défis.
Aujourd'hui, nous allons explorer chacun de ces mécanismes successivement et comprendre leurs différences. Commençons par le Semaphore.
Les Semaphore en Java sont implémentés via la classe java.util.concurrent.Semaphore.
Constructeurs
Semaphore(int permits) : Constructeur qui crée un semaphore avec un nombre défini d'autorisations. Les autorisations représentent le nombre d'accès à la ressource partagée.
Main.java
1Semaphore semaphore = new Semaphore(20);
Semaphore(int permits, boolean fair) : Constructeur qui assure une résolution premier arrivé, premier servi.
Main.java
1Semaphore semaphore = new Semaphore(20, true);
Si fair est défini sur true, le semaphore accordera les permissions selon l'ordre premier entré, premier sorti (FIFO), ce qui permet d'éviter la famine. Par défaut - false.
Méthodes principales
La méthode acquire() demande une permission unique. Si une permission est disponible, elle est accordée immédiatement ; sinon, le thread est bloqué jusqu'à ce qu'une permission soit disponible. Une fois la tâche terminée, la méthode release() est utilisée pour libérer la permission, la rendant à nouveau disponible dans le semaphore. Si d'autres threads attendaient une permission, l'un d'eux sera débloqué.
Imaginez un parking avec un nombre limité de places. Le semaphore agit comme un contrôleur, surveillant les places disponibles et refusant l'accès une fois le parking complet.
Main.java
1234567891011121314151617181920212223242526272829303132package 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(); } } }
Il est également possible de connaître le nombre de permissions actuellement disponibles dans Semaphore à l'aide de la méthode int availablePermits(). Il est aussi possible d'essayer d'obtenir une permission en utilisant la méthode boolean tryAcquire(), qui retourne true si une permission a été obtenue et false sinon.
Main.java
1234567891011121314151617181920212223242526272829303132333435363738394041package 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()); } } } }
En d'autres termes, un Semaphore est utile lorsque vous devez fournir un accès limité et simultané à un segment de code spécifique. Le seul inconvénient est le risque potentiel de deadlock si les threads sont bloqués dans le mauvais ordre.
Passons maintenant au prochain mécanisme de synchronisation, encore plus simple à utiliser mais qui sera totalement adapté à vos besoins.
CyclicBarrier
Barrier en Java est représenté par la classe java.util.concurrent.CyclicBarrier. Les principales méthodes de CyclicBarrier incluent :
Constructeurs CyclicBarrier
CyclicBarrier(int parties) : Constructeur qui crée une barrière qui bloque les threads jusqu'à ce qu'un certain nombre de threads (parties) soient arrivés.
Main.java
1CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
CyclicBarrier(int parties, Runnable barrierAction) : Constructeur qui crée une barrière avec un nombre donné de participants et une action (barrierAction) exécutée lorsque tous les participants atteignent la barrière.
Main.java
1234567Runnable 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);
Méthodes de CyclicBarrier
La méthode principale await() utilisée comme barrière empêche le thread de continuer tant que tous les threads n'ont pas atteint cette méthode. Retourne un numéro de séquence indiquant l'ordre d'arrivée des participants.
Main.java
1234567891011121314151617181920212223242526272829303132333435363738394041424344package 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(); } } } }
Il peut arriver que tous les threads n’atteignent pas la barrière et que le programme se bloque ; à cet effet, la méthode int await(long timeout, TimeUnit unit) est utilisée, similaire à await(), mais avec un délai d’attente. Si le délai expire avant que tous les participants n’arrivent, la méthode génère une exception TimeoutException.
Il est également possible de connaître le nombre de participants requis pour atteindre la barrière avec int getParties(), ainsi que la méthode similaire int getNumberWaiting() qui retourne le nombre de participants actuellement en attente à la barrière.
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849package 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()); } } }
Il est également possible de vérifier si la barrière a été détruite lorsqu’un des threads est interrompu ou que le délai d’attente a expiré, en utilisant la méthode boolean isBroken(). Si elle a été rompue, il est possible d’utiliser la méthode void reset() qui restaurera simplement la barrière.
Main.java
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(); }
Il convient de prendre en compte que certains flux peuvent ne pas atteindre la barrière en raison d'une erreur ou d'autres raisons, ce qui implique que la barrière n'autorisera pas les flux actuellement en attente à la barrière.
Merci pour vos commentaires !
Demandez à l'IA
Demandez à l'IA
Posez n'importe quelle question ou essayez l'une des questions suggérées pour commencer notre discussion
Can you explain the main differences between Semaphore and CyclicBarrier?
How do I decide when to use Semaphore versus CyclicBarrier in my program?
Can you provide examples of real-world scenarios for each synchronization mechanism?
Awesome!
Completion rate improved to 3.33
Sémaphore et Barrière
Glissez pour afficher le menu
Dans les programmes multithreadés, il est souvent nécessaire de contrôler l'accès aux ressources ou de synchroniser l'exécution des threads. Semaphore et Barrier sont des mécanismes de synchronisation de haut niveau qui permettent de relever ces défis.
Aujourd'hui, nous allons explorer chacun de ces mécanismes successivement et comprendre leurs différences. Commençons par le Semaphore.
Les Semaphore en Java sont implémentés via la classe java.util.concurrent.Semaphore.
Constructeurs
Semaphore(int permits) : Constructeur qui crée un semaphore avec un nombre défini d'autorisations. Les autorisations représentent le nombre d'accès à la ressource partagée.
Main.java
1Semaphore semaphore = new Semaphore(20);
Semaphore(int permits, boolean fair) : Constructeur qui assure une résolution premier arrivé, premier servi.
Main.java
1Semaphore semaphore = new Semaphore(20, true);
Si fair est défini sur true, le semaphore accordera les permissions selon l'ordre premier entré, premier sorti (FIFO), ce qui permet d'éviter la famine. Par défaut - false.
Méthodes principales
La méthode acquire() demande une permission unique. Si une permission est disponible, elle est accordée immédiatement ; sinon, le thread est bloqué jusqu'à ce qu'une permission soit disponible. Une fois la tâche terminée, la méthode release() est utilisée pour libérer la permission, la rendant à nouveau disponible dans le semaphore. Si d'autres threads attendaient une permission, l'un d'eux sera débloqué.
Imaginez un parking avec un nombre limité de places. Le semaphore agit comme un contrôleur, surveillant les places disponibles et refusant l'accès une fois le parking complet.
Main.java
1234567891011121314151617181920212223242526272829303132package 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(); } } }
Il est également possible de connaître le nombre de permissions actuellement disponibles dans Semaphore à l'aide de la méthode int availablePermits(). Il est aussi possible d'essayer d'obtenir une permission en utilisant la méthode boolean tryAcquire(), qui retourne true si une permission a été obtenue et false sinon.
Main.java
1234567891011121314151617181920212223242526272829303132333435363738394041package 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()); } } } }
En d'autres termes, un Semaphore est utile lorsque vous devez fournir un accès limité et simultané à un segment de code spécifique. Le seul inconvénient est le risque potentiel de deadlock si les threads sont bloqués dans le mauvais ordre.
Passons maintenant au prochain mécanisme de synchronisation, encore plus simple à utiliser mais qui sera totalement adapté à vos besoins.
CyclicBarrier
Barrier en Java est représenté par la classe java.util.concurrent.CyclicBarrier. Les principales méthodes de CyclicBarrier incluent :
Constructeurs CyclicBarrier
CyclicBarrier(int parties) : Constructeur qui crée une barrière qui bloque les threads jusqu'à ce qu'un certain nombre de threads (parties) soient arrivés.
Main.java
1CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
CyclicBarrier(int parties, Runnable barrierAction) : Constructeur qui crée une barrière avec un nombre donné de participants et une action (barrierAction) exécutée lorsque tous les participants atteignent la barrière.
Main.java
1234567Runnable 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);
Méthodes de CyclicBarrier
La méthode principale await() utilisée comme barrière empêche le thread de continuer tant que tous les threads n'ont pas atteint cette méthode. Retourne un numéro de séquence indiquant l'ordre d'arrivée des participants.
Main.java
1234567891011121314151617181920212223242526272829303132333435363738394041424344package 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(); } } } }
Il peut arriver que tous les threads n’atteignent pas la barrière et que le programme se bloque ; à cet effet, la méthode int await(long timeout, TimeUnit unit) est utilisée, similaire à await(), mais avec un délai d’attente. Si le délai expire avant que tous les participants n’arrivent, la méthode génère une exception TimeoutException.
Il est également possible de connaître le nombre de participants requis pour atteindre la barrière avec int getParties(), ainsi que la méthode similaire int getNumberWaiting() qui retourne le nombre de participants actuellement en attente à la barrière.
Main.java
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849package 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()); } } }
Il est également possible de vérifier si la barrière a été détruite lorsqu’un des threads est interrompu ou que le délai d’attente a expiré, en utilisant la méthode boolean isBroken(). Si elle a été rompue, il est possible d’utiliser la méthode void reset() qui restaurera simplement la barrière.
Main.java
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(); }
Il convient de prendre en compte que certains flux peuvent ne pas atteindre la barrière en raison d'une erreur ou d'autres raisons, ce qui implique que la barrière n'autorisera pas les flux actuellement en attente à la barrière.
Merci pour vos commentaires !