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 ?

*

*

Un conseil Symfony2

Comment personnaliser la page 404 ?

Pour personnaliser la page 404 sous Symfony2 il faut surcharger le contrôleur par défaut. On peut l'achever en déterminant "exception_controller" dans le fichier de configuration. Supposons que nous utilisons le bundle ExceptionsErrorBundle pour gérer toute sorte des exceptions. Un des contrôleurs (NotFoundController) s'occupe de manipulations liées aux erreurs 404. Pour pouvoir utiliser ce contrôleur pour les erreurs type 404, il faut le déclarer dans le fichier de configuration (config.yml, config_dev.yml - en fonction de l'environnement) :

twig: 
    exception_controller: "ExceptionsErrorBundle :NotFound:handleException"