Y a-t-il un avantage à utiliser
Java.util.concurrent.CountdownLatch
au lieu de
Java.util.concurrent.Semaphore ?
Autant que je sache, les fragments suivants sont presque équivalents:
1. Sémaphore
final Semaphore sem = new Semaphore(0);
for (int i = 0; i < num_threads; ++ i)
{
Thread t = new Thread() {
public void run()
{
try
{
doStuff();
}
finally
{
sem.release();
}
}
};
t.start();
}
sem.acquire(num_threads);
2: CountDownLatch
final CountDownLatch latch = new CountDownLatch(num_threads);
for (int i = 0; i < num_threads; ++ i)
{
Thread t = new Thread() {
public void run()
{
try
{
doStuff();
}
finally
{
latch.countDown();
}
}
};
t.start();
}
latch.await();
Sauf que dans le cas # 2, le verrou ne peut pas être réutilisé et, plus important encore, vous devez savoir à l'avance combien de threads seront créés (ou attendre qu'ils soient tous démarrés avant de créer le verrou).
Dans quelle situation le verrou pourrait-il donc être préférable?
Le verrou CountDown est fréquemment utilisé pour l'exact opposé de votre exemple. Généralement, vous auriez de nombreux threads bloqués sur "wait ()" qui démarreraient tous simultanément lorsque le compte à rebours atteindrait zéro.
final CountDownLatch countdown = new CountDownLatch(1);
for (int i = 0; i < 10; ++ i){
Thread racecar = new Thread() {
public void run() {
countdown.await(); //all threads waiting
System.out.println("Vroom!");
}
};
racecar.start();
}
System.out.println("Go");
countdown.countDown(); //all threads start now!
Vous pouvez également l'utiliser comme une "barrière" de style MPI qui oblige tous les threads à attendre que d'autres threads rattrapent un certain point avant de continuer.
final CountDownLatch countdown = new CountDownLatch(num_thread);
for (int i = 0; i < num_thread; ++ i){
Thread t= new Thread() {
public void run() {
doSomething();
countdown.countDown();
System.out.printf("Waiting on %d other threads.",countdown.getCount());
countdown.await(); //waits until everyone reaches this point
finish();
}
};
t.start();
}
Cela dit, le verrou CountDown peut être utilisé en toute sécurité de la manière que vous avez montrée dans votre exemple.
CountDownLatch est utilisé pour démarrer une série de threads puis attendre jusqu'à ce qu'ils soient tous terminés (ou jusqu'à ce qu'ils appellent countDown()
un nombre donné de fois.
Le sémaphore est utilisé pour contrôler le nombre de threads simultanés qui utilisent une ressource. Cette ressource peut être quelque chose comme un fichier, ou pourrait être le processeur en limitant le nombre de threads en cours d'exécution. Le compte sur un sémaphore peut monter et descendre lorsque différents threads appellent acquire()
et release()
.
Dans votre exemple, vous utilisez essentiellement Sémaphore comme une sorte de compte [~ # ~] up [~ # ~] Latch. Étant donné que votre intention est d'attendre la fin de tous les threads, l'utilisation de CountdownLatch
rend votre intention plus claire.
Bref résumé:
Sémaphore et CountDownLatch sert un but différent.
Utilisez Semaphore pour contrôler l'accès des threads à la ressource.
Utilisez CountDownLatch pour attendre la fin de tous les threads
Définition du sémaphore à partir des javadocs:
A Semaphore maintient un ensemble de permis. Chaque bloc acquiert () si nécessaire jusqu'à ce qu'un permis soit disponible, puis le prend. Chaque release () ajoute un permis, libérant potentiellement un acquéreur bloquant.
Cependant, aucun objet de permis réel n'est utilisé; le Sémaphore ne fait que compter le nombre disponible et agit en conséquence.
Comment ça marche?
Les sémaphores sont utilisés pour contrôler le nombre de threads simultanés qui utilisent une ressource. Cette ressource peut être quelque chose comme une donnée partagée ou un bloc de code ( section critique ) ou tout fichier.
Le compte sur un sémaphore peut monter et descendre lorsque différents threads appellent acquire
() et release
(). Mais à tout moment, vous ne pouvez pas avoir plus de nombre de threads que le nombre de sémaphores.
Cas d'utilisation du sémaphore:
Jetez un oeil à ceci article pour les utilisations de sémaphore.
Définition CountDownLatch à partir de javadocs:
Aide à la synchronisation qui permet à un ou plusieurs threads d'attendre la fin d'un ensemble d'opérations en cours dans d'autres threads.
Comment ça marche?
CountDownLatch fonctionne en ayant un compteur initialisé avec le nombre de threads, qui est décrémenté chaque fois qu'un thread termine son exécution. Lorsque le nombre atteint zéro, cela signifie que tous les threads ont terminé leur exécution et que les threads en attente de verrouillage reprennent l'exécution.
CountDownLatch Cas d'utilisation:
Jetez un œil à cela article pour comprendre clairement les concepts de CountDownLatch.
Jetez un œil à Fork Join Pool à ceci article aussi. Il a quelques similitudes avec CountDownLatch.
Dites que vous êtes entré dans le magasin de golf professionnel, en espérant trouver un quatuor,
Lorsque vous faites la queue pour obtenir une heure de départ de l'un des préposés de la boutique en ligne, vous avez essentiellement appelé proshopVendorSemaphore.acquire()
, une fois que vous avez obtenu une heure de départ, vous avez appelé proshopVendorSemaphore.release()
. Remarque: les accompagnateurs gratuits peuvent vous servir, c'est-à-dire des ressources partagées.
Maintenant que vous vous dirigez vers le démarreur, il démarre une CountDownLatch(4)
et appelle await()
pour attendre les autres, pour votre partie que vous avez appelée check-in ie CountDownLatch
.countDown()
et le reste du quatuor aussi. Quand tout arrive, le starter donne le coup d'envoi (await()
retour d'appel)
Maintenant, après neuf trous lorsque chacun de vous fait une pause, laisse hypothétiquement impliquer à nouveau le démarreur, il utilise une 'nouvelle' CountDownLatch(4)
pour lancer le trou 10, même attente/synchronisation que le trou 1.
Cependant, si le démarreur a utilisé un CyclicBarrier
pour commencer, il aurait pu réinitialiser la même instance dans le trou 10 au lieu d'un deuxième verrou, qui utilise & throw.
En regardant la source disponible gratuitement, il n'y a pas de magie dans l'implémentation des deux classes, donc leurs performances devraient être sensiblement les mêmes. Choisissez celui qui rend votre intention plus évidente.
CountdownLatch oblige les threads à attendre la méthode wait () jusqu'à ce que le compte atteigne zéro. Alors peut-être que vous voulez que tous vos threads attendent jusqu'à 3 invocations de quelque chose, alors tous les threads peuvent aller. Un verrou ne peut généralement pas être réinitialisé.
Un sémaphore permet aux threads de récupérer les autorisations, ce qui empêche trop de threads de s'exécuter à la fois, bloquant s'il ne peut pas obtenir les autorisations nécessaires pour continuer. Les autorisations peuvent être retournées à un sémaphore permettant aux autres threads en attente de continuer.