CountDownLatch

Synchronisation des threads avec CountDownLatch

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com

Dans l'article consacré à CyclicBarrier dans Java on a abordé la problématique de l'exécution des threads les uns après les autres. Une autre classe du package concurrent qui permet de synchroniser le travail de plusieurs threads s'appelle CountDownLatch.

Qu'est-ce que c'est CountDownLatch en Java

CountDownLatch est une classe qui aide à travailler avec la synchronisation de plusieurs threads. Elle peut être utilisée dans la situation où quelques threads doivent attendre la fin d'exécution d'un ou de plusieurs autres threads.

Pour être plus précis, le CountDownLatch prendre en paramètre de son constructeur le nombre de threads qui doivent être exécutés afin que la tâche comprise dans un autre thread puisse être invoquée. On suppose que le nombre est spécifié à 3 et que le thread qui attend (il appelle de la méthode CountDownLatch.await()) est "Thread A". Les threads "1", "2" et "3" s'exécutent à leur tour et se terminent par l'appel de la méthode CountDownLatch.countDown(). Cette méthode fait en sorte que le compteur décrémente. Quand il atteint 0, la tâche du "Thread A", mis au début en attente, peut être exécutée.

Exemple CountDownLatch

On peut comprendre cette spécificité grâce à l'exemple de la préparation d'une commande. Notre commande est composée de 4 produits. Tout d'abord on doit retrouver ces produits dans notre stock. Après, ils devront être placés dans un emballage cadeau.

On aura deux classes. La première, LatchOrder, va jouer le rôle de la personne qui vérifie la conformité de la commande ainsi que lance la recherche des produits. La recherche des produits sera représentée par la classe Package. Regardons cela sur l'exemple du code :

import java.util.concurrent.CountDownLatch;
import java.util.List;
import java.util.ArrayList;

public class LatchOrder {
    private String[] items = new String[] {"chocolat", "candy", "wafer", "cocoa"};
    public static List packageItems = new ArrayList();
    
    public static void main(String[] args) {
        LatchOrder latchOrder = new LatchOrder();
        latchOrder.start();
    }
    
    public void start() {
        try {
            CountDownLatch startLatch = new CountDownLatch(1); 
            CountDownLatch latch = new CountDownLatch(items.length); 
            for (int i = 0; i < items.length; i++) {
                new Thread(new Package(items[i], (i*2000), startLatch, latch)).start(); 
            }
            System.out.println("Firstly, I'm doing some items and order verifications. Please wait 8 seconds...");
            Thread.sleep(8000);
            System.out.println("OK, The order is valid. I can relase my lock (countDown()) and wait for Package threads to end with await() method.");
            startLatch.countDown();
            latch.await();
            System.out.println("Package is ready with following items : " + packageItems);
        } catch (Exception e) {
            System.out.println("An exception occured : " + e.getMessage());
        }
    }
}

class Package implements Runnable {
    private String name;
    private int sleep;
    private CountDownLatch startLatch;
    private CountDownLatch latch;
    
    public Package(String name, int sleep, CountDownLatch startLatch, CountDownLatch latch) {
        this.name = name;
        this.sleep = sleep;
        this.startLatch = startLatch;
        this.latch = latch;
    }
    
    public void run() {
        try {
            startLatch.await();
            Thread.sleep(sleep);
            System.out.println("I found " + name + " in my stock. I add it into my order package");
            LatchOrder.packageItems.add(name);
            latch.countDown();
        } catch (Exception e) {
            System.out.println("An exception occured on running package " + name + " : " + e.getMessage());
        }
    }
}

Le résultat de l'exécution est le suivant :

Firstly, I'm doing some items and order verifications. Please wait 8 seconds...
OK, The order is valid. I can relase my lock (countDown()) and wait for Package threads to end with await() method.
I found chocolat in my stock. I add it into my order package
I found candy in my stock. I add it into my order package
I found wafer in my stock. I add it into my order package
I found cocoa in my stock. I add it into my order package
Package is ready with following items : [chocolat, candy, wafer, cocoa]

On voit bien que les tâches ont été exécutés les uns après les autres. On remarque aussi que LatchOrder a attendu que tous les produits soient trouvés afin de préparer l'emballage (le dernier message). Regardons maintenant le message qu'on va voir quand on commentera tous les appels des méthodes await() et countDown() :

I found chocolat in my stock. I add it into my order package
Firstly, I'm doing some items and order verifications. Please wait 8 seconds...
I found candy in my stock. I add it into my order package
I found wafer in my stock. I add it into my order package
I found cocoa in my stock. I add it into my order package
OK, The order is valid. I can relase my lock (countDown()) and wait for Package threads to end with await() method.
Package is ready with following items : [chocolat, candy, wafer, cocoa]

La préparation de la commande est donc complètement désordonnée. Quelle est donc la relation entre await() et countDown() ? Pour mieux comprendre cette interdépendance, décomposons le processus en 4 étapes :

Etape latchOrder item : chocolat item : candy item : wafer item : cocoa
1 initialisation et démarrage initialisation et démarrage initialisation et démarrage initialisation et démarrage initialisation et démarrage
2 s'endort pour 8 secondes pour valider la commande démarre, mais attend la fin de l'exécution du latchOrder (startLatch.await()) démarre, mais attend la fin de l'exécution du latchOrder (startLatch.await()) démarre, mais attend la fin de l'exécution du latchOrder (startLatch.await()) démarre, mais attend la fin de l'exécution du latchOrder (startLatch.await())
3 au bout de 8 secondes, le nombre des choses à faire diminue à 0 avec startLatch.countDown(). latchOrder autorise alors les threads avec 4 produits à s'exécuter. Il attend aussi que tous les 4 soient terminés (méthode latch.await()) avant d'exécuter sa dernière tâche (affichage du message de confirmation). s'endort et affiche le message que le produit a été trouvé et rajouté au paquet. Avec latch.countDown() il diminue le nombre des tâches à terminer au groupe des produits (3 restants) s'endort et affiche le message que le produit a été trouvé et rajouté au paquet. Avec latch.countDown() il diminue le nombre des tâches à terminer au groupe des produits (2 restants) s'endort et affiche le message que le produit a été trouvé et rajouté au paquet. Avec latch.countDown() il diminue le nombre des tâches à terminer au groupe des produits (1 restant) s'endort et affiche le message que le produit a été trouvé et rajouté au paquet. Avec latch.countDown() il diminue le nombre des tâches à terminer au groupe des produits. Toutes les tâches du groupe sont désormais exécutées.
4 latchOrder est alors rélaché. Le message de confirmation peut être affiché ("Package is ready with following items : [chocolat, candy, wafer, cocoa]") - - - -
Bartosz KONIECZNY Concurrence

Une question ? Une remarque ?

*

*

Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony2

Comment importer les routes ?

Parfois on a besoin de partager les routes en fonction de différents environnements (prod, dev / frontend, backend). Symfony2 gère ces cas très facilement. Pour pouvoir partager les routes il suffit tout simplement d'extraire les routes communes et de les importer par la suite. Dans notre exemple on a besoin de partager les routes qui définissent les requêtes AJAX. Pour ce faire, on va les sauvegarder dans le fichier routingAjax.yml, placé dans un bundle qui s'appelle RequestsAjaxBundle/Resource/config/routingAjax.yml. Alors, pour l'utiliser dans le fichier pour des routes de backend, il suffit de l'importer dans l'endroit voulu, comme ici :

# Ajax actions
ajaxRoutes:
    resource: "@RequestsAjaxBundle/Resources/config/routingAjax.yml"