Java CyclicBarrier vs CountDownLatch

1. Introduction

Dans ce didacticiel, nous comparerons CyclicBarrier et CountDownLatch et essayerons de comprendre les similitudes et les différences entre les deux.

2. Quels sont-ils?

En matière de concurrence d'accès, il peut être difficile de conceptualiser ce que chacun est censé accomplir.

Tout d'abord, CountDownLatch et CyclicBarrier sont utilisés pour gérer les applications multithreads .

Et ils sont tous deux destinés à exprimer comment un thread ou un groupe de threads donné doit attendre.

2.1. CountDownLatch

Un CountDownLatch est une construction sur laquelle un thread attend pendant que d'autres threads comptent à rebours sur le verrou jusqu'à ce qu'il atteigne zéro.

Nous pouvons penser à cela comme un plat dans un restaurant en cours de préparation. Quel que soit le cuisinier qui prépare le nombre des n articles, le serveur doit attendre que tous les articles soient dans l'assiette. Si une assiette prend n articles, n'importe quel cuisinier compte à rebours sur le loquet pour chaque article qu'il met sur l'assiette.

2.2. CyclicBarrier

Un CyclicBarrier est une construction réutilisable dans laquelle un groupe de threads attend ensemble jusqu'à ce que tous les threads arrivent . À ce stade, la barrière est rompue et une action peut éventuellement être entreprise.

Nous pouvons penser à cela comme à un groupe d'amis. Chaque fois qu'ils prévoient de manger dans un restaurant, ils décident d'un point commun où ils peuvent se rencontrer. Ils s'y attendent , et ce n'est que lorsque tout le monde arrive qu'ils peuvent aller au restaurant pour manger ensemble.

2.3. Lectures complémentaires

Et pour plus de détails sur chacun d'eux individuellement, reportez-vous à nos précédents tutoriels sur CountDownLatch et CyclicBarrier respectivement.

3. Tâches vs threads

Examinons plus en détail certaines des différences sémantiques entre ces deux classes.

Comme indiqué dans les définitions, CyclicBarrier permet à un certain nombre de threads de s'attendre les uns sur les autres, tandis que CountDownLatch permet à un ou plusieurs threads d'attendre qu'un certain nombre de tâches se terminent.

En bref, CyclicBarrier gère un nombre de threads tandis que CountDownLatch gère un nombre de tâches .

Dans le code suivant, nous définissons un CountDownLatch avec un nombre de deux. Ensuite, nous appelons countDown () deux fois à partir d'un seul thread:

CountDownLatch countDownLatch = new CountDownLatch(2); Thread t = new Thread(() -> { countDownLatch.countDown(); countDownLatch.countDown(); }); t.start(); countDownLatch.await(); assertEquals(0, countDownLatch.getCount());

Une fois que le verrou atteint zéro, l'appel d' attente revient.

Notez que dans ce cas, nous avons pu faire en sorte que le même thread diminue le nombre deux fois.

CyclicBarrier, cependant, est différent sur ce point.

Similaire à l'exemple ci-dessus, nous créons un CyclicBarrier, à nouveau avec un nombre de deux et appelons await () dessus, cette fois à partir du même thread:

CyclicBarrier cyclicBarrier = new CyclicBarrier(2); Thread t = new Thread(() -> { try { cyclicBarrier.await(); cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { // error handling } }); t.start(); assertEquals(1, cyclicBarrier.getNumberWaiting()); assertFalse(cyclicBarrier.isBroken());

La première différence ici est que les threads qui attendent sont eux-mêmes la barrière.

Deuxièmement, et plus important encore, le deuxième await () est inutile . Un seul thread ne peut pas compter deux fois une barrière.

En effet, parce que t doit attendre pour un autre thread d'appeler await () - pour amener le nombre à deux - t « s deuxième appel à await () ne fait être invoquée jusqu'à ce que la barrière est déjà cassé!

Dans notre test, la barrière n'a pas été franchie car nous n'avons qu'un seul thread en attente et non les deux threads qui seraient nécessaires pour que la barrière soit déclenchée. Cela ressort également de la méthode cyclicBarrier.isBroken () , qui renvoie false .

4. Réutilisabilité

La deuxième différence la plus évidente entre ces deux classes est la réutilisabilité. Pour préciser, lorsque la barrière se déclenche dans CyclicBarrier , le décompte revient à sa valeur d'origine. CountDownLatch est différent car le décompte ne se réinitialise jamais.

Dans le code donné, nous définissons un CountDownLatch avec le compte 7 et le comptons à travers 20 appels différents:

CountDownLatch countDownLatch = new CountDownLatch(7); ExecutorService es = Executors.newFixedThreadPool(20); for (int i = 0; i  { long prevValue = countDownLatch.getCount(); countDownLatch.countDown(); if (countDownLatch.getCount() != prevValue) { outputScraper.add("Count Updated"); } }); } es.shutdown(); assertTrue(outputScraper.size() <= 7);

Nous observons que même si 20 threads différents appellent countDown () , le nombre ne se réinitialise pas une fois qu'il atteint zéro.

Semblable à l'exemple ci-dessus, nous définissons un CyclicBarrier avec le compte 7 et attendons dessus à partir de 20 threads différents:

CyclicBarrier cyclicBarrier = new CyclicBarrier(7); ExecutorService es = Executors.newFixedThreadPool(20); for (int i = 0; i  { try { if (cyclicBarrier.getNumberWaiting()  7);

Dans ce cas, on observe que la valeur diminue à chaque fois qu'un nouveau thread s'exécute, en se remettant à la valeur d'origine, une fois qu'elle atteint zéro.

5. Conclusion

Dans l'ensemble, CyclicBarrier et CountDownLatchsont deux outils utiles pour la synchronisation entre plusieurs threads. Cependant, ils sont fondamentalement différents en termes de fonctionnalités qu'ils fournissent. Considérez soigneusement chacun d'entre eux pour déterminer celui qui convient à l'emploi.

Comme d'habitude, tous les exemples discutés sont accessibles sur Github.