Thread safety

Sécurité des threads

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

Sous la notion de thread safety se cache la sécurité des threads. Or, parfois les applications multi threads peuvent poser beaucoup de soucis si elles ne sont pas correctement synchronisées. Imaginons la situation où on construit une maison et les maçons posent les murs avant que les ouvriers bâtissent les fondements. L'article ci-dessous traitera cette problématique, mais dans le monde Java. On verra comment se protéger contre la désynchronisation et les bugs y liés.

Thread safety en Java

La notion thread safety veut dire que les attributs et les instances sont valides pour tous les Threads y accédant. Autrement dit, une application l'appliquant doit avoir le même comportement pour un et plusieurs tâches. Voici les régles qu'une application devrait respecter pour qu'elle évite au maximum les problèmes de synchronisation :

  1. Utiliser les variables locales.

    Elles peuvent être très utiles pour préserver la logique de l'application. La surété concerne avant tout les types primitifs qui localement sont toujours stockés dans la mémoire du Thread.

    En ce qui concerne les objets, ils ne fonctionnent pas de la même manière. Même s'ils sont créés localement, ils sont placés dans la mémoire partagée. Ces instances sont sécurisées jusqu'au moment où elles ne quittent pas la méthode dans laquelle elles ont été initialisées. Pour rendre ces références disponibles à une autre fonction, on peut par exemple les passer en paramètre de cette fonction, comme ici :

    public void initObject() {
        MyNewObject obj = new MyNewObject("#O1");
        handleObjectName(obj);
    }
    private void handleObjectName(MyNewObject o) {
      
    }
    
  2. Utiliser les objets immuables.

    Comme on l'a évoqué ci-dessus, les objets immuables sont privés de toutes les méthodes pouvant modifier leurs attributs. Chaque création d'un objet immuable nécessite donc la création d'une nouvelle instance de la classe. Il est donc impossible que deux Threads différents puissent modifier le modifier.

    Cependant, il est très important de supprimer toutes les méthodes pouvant changer la caractéristique de l'objet. Sinon sa référence risque de rencontrer les problèmes de thread safety. Si l'on est obligés de placer une fonction de ce type, on peut s'en sortir en créant une nouvelle instance, comme ici :

    class ImmutableClass {
        private String name;
        public ImmutableClass(String n) {
            name = n;
        }
      
        public ImmutableClass changeTheName(String newName) {
            return new ImmutableClass(newName);
        }
    }
    
  3. Concevoir l'application qui fonctionnera correctement pour un seul Thread.

    Afin d'exclure les problèmes fonctionnels et ne pas les mélanger avec ceux du multi threading, il faut commencer par développer l'application qui fonctionnera correctement pour un seul Thread. Dans le cas contraire dans certains situations on risque de perdre un peu de temps avant de comprendre que le bug ne doit pas être lié à la synchronisation.

  4. Implémenter correctement le principe ACID.

    En premier lieu, le principe ACID (Atomicity - Consistency - Isolation - Durability) concerne les bases de données. Cependant, il trouve son utilité dans la programmation concurrentielle. Pour le comprendre, voici la description des mots composant cet acronyme :
    - Atomicity (atomicité) : chaque opération doit être atomique. Elle doit s'exécuter complétement ou ne pas s'exécuter du tout. Ceci pour éviter les situations déjà évoquées où un Thread récupère la valeur d'un attribut synchronisé qui est en cours d'être modifié (par exemple la récupération de 5 éléments d'une Collection, tandis qu'elle en contient 60).
    - Consistency (cohérence) : dans la base de données il s'agit de cohérence entre les données sauvegardées et les règles définies. En programmation concurrentielle on peut résumer ce point à la cohérence des données partagées par plusieurs Threads. La modification d'un attribut devrait être accessible pour d'autres tâches qui en ont besoin.
    - Isolation (isolation) : les tâches concurrentielles devrait avoir le même effet que si elles étaient exécutées séquentiellement (l'une après l'autre).
    - Durability (durabilité) : le seul point qui peut être difficilement appliqué à la programmation concurrentielle. En base de données il s'agit des données qui seront correctes même dans le cas d'une faille du matériel. En multi threading il peut s'agir de l’accessibilité des données à plusieurs Threads pendant un temps d'exécution précis.

  5. Synchroniser les sections critiques.

    Les fragments critiques du code correspondent à des endroits qui peuvent être utilisé par, au maximum, un seul Thread à un moment donné.

    Ces sections doivent être atomiques. Autrement dit, elles doivent s'exécuter complétement avant que d'autres Threads voient le résultat de leur travail. Cela permet d'éviter les situations où un Thread récupère les données qui sont en train d'être traitées (par exemple récupère seulement 5 éléments d'une collection au lieu de 10 qui ont été soumis).

  6. Mettre les attributs synchronisés privés.

    Tous les attributs d'une classe doivent être privés. Dans le cas contraire (accès publique), une classe peut ignorer leur synchronisation en récupérant la valeur au dehors d'une méthode synchronisée. Cependant, seuls les attributs qui participent dans le processus de coordination doivent être privés.

  7. Ecrire un wrapper qui est thread safety.

    On peut également créer un wrapper (enveloppeur) pour une instance qui nécessite la synchronisation. Il s'agit de placer l'objet synchronisé dans une autre classe. Voici l'exemple de cette technique :

    class SafeHouse {
        private House house;
      
        public SafeHouse(int rooms, int visitors) {
            house = new House(rooms, visitors);
        }
      
        public synchronized void setRooms(int r) {
            house.setRooms(r);
        }
      
        public synchronized int getRooms() {
            return house.getRooms();
        }
    }
    
  8. Utiliser volatile.

    On l'a déjà mentionné. Le mot-clé volatile indique qu'un attribut ne peut pas être placé dans le cache local d'un Thread et qu'il doit être lu depuis la mémoire partagée par plusieurs tâches. Cela empêche également le JVM de reordonner le code à sa façon au moment de compilation.

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 récupérer tous les paramètres ?

Pour récupérer tous les paramètres dans Symfony2, il faut appeler la méthode $request->request->all() (où $request est l'instance de la classe Symfony\Component\HttpFoundation\Request.