3.1 Objet actif

Un objet Active est un modèle de conception qui sépare le thread d'exécution d'une méthode du thread dans lequel elle a été appelée. Le but de ce modèle est de fournir une exécution parallèle à l'aide d'appels de méthode asynchrones et d'un planificateur de traitement des demandes.

Version simplifiée :

objet actif

Variante classique :

Objet actif 2

Ce modèle comporte six éléments :

  • Un objet proxy qui fournit une interface aux méthodes publiques du client.
  • Une interface qui définit les méthodes d'accès pour l'objet actif.
  • Liste des demandes entrantes des clients.
  • Un planificateur qui détermine l'ordre dans lequel les requêtes doivent être exécutées.
  • Implémentation de méthodes d'objets actifs.
  • Une procédure de rappel ou une variable pour que le client reçoive un résultat.

3.2 serrure

Le modèle de verrouillage est un mécanisme de synchronisation qui permet un accès exclusif à une ressource partagée entre plusieurs threads. Les verrous sont un moyen d'appliquer la politique de contrôle de la concurrence.

Fondamentalement, un verrou logiciel est utilisé, avec l'hypothèse que chaque thread essaie «d'acquérir le verrou» avant d'accéder à la ressource partagée correspondante.

Cependant, certains systèmes fournissent un mécanisme de verrouillage obligatoire par lequel une tentative d'accès non autorisé à une ressource verrouillée sera annulée en levant une exception sur le thread qui a tenté d'accéder.

Un sémaphore est le type de serrure le plus simple. En termes d'accès aux données, aucune distinction n'est faite entre les modes d'accès : partagé (lecture seule) ou exclusif (lecture-écriture). En mode partagé, plusieurs threads peuvent demander un verrou pour accéder aux données en mode lecture seule. Le mode d'accès exclusif est également utilisé dans les algorithmes de mise à jour et de suppression.

schéma de verrouillage

Les types de verrous se distinguent par la stratégie de blocage de la poursuite de l'exécution du thread. Dans la plupart des implémentations, une demande de verrou empêche le thread de continuer à s'exécuter jusqu'à ce que la ressource verrouillée soit disponible.

Un spinlock est un verrou qui attend dans une boucle jusqu'à ce que l'accès soit accordé. Un tel verrou est très efficace si un thread attend un verrou pendant un court laps de temps, évitant ainsi une replanification excessive des threads. Le coût d'attente pour l'accès sera important si l'un des threads détient le verrou pendant une longue période.

schéma de verrouillage 2

Pour implémenter efficacement le mécanisme de verrouillage, une prise en charge au niveau matériel est nécessaire. Le support matériel peut être implémenté comme une ou plusieurs opérations atomiques telles que "test-and-set", "fetch-and-add" ou "compare-and-swap". De telles instructions vous permettent de vérifier sans interruption que le verrou est libre et, le cas échéant, d'acquérir le verrou.

3.3 Moniteur

Le modèle Monitor est un mécanisme d'interaction et de synchronisation de processus de haut niveau qui permet d'accéder à des ressources partagées. Approche de synchronisation de deux tâches informatiques ou plus à l'aide d'une ressource commune, généralement du matériel ou un ensemble de variables.

Dans le multitâche basé sur un moniteur, le compilateur ou l'interpréteur insère de manière transparente du code de verrouillage-déverrouillage dans des routines formatées de manière appropriée, de manière transparente pour le programmeur, évitant au programmeur d'appeler explicitement des primitives de synchronisation.

Le moniteur se compose de :

  • un ensemble de procédures qui interagissent avec une ressource partagée
  • mutex
  • variables associées à cette ressource
  • un invariant qui définit les conditions pour éviter une condition de concurrence

La procédure de surveillance acquiert le mutex avant de commencer le travail et le maintient soit jusqu'à ce que la procédure se termine, soit jusqu'à ce qu'une certaine condition soit attendue. Si chaque procédure garantit que l'invariant est vrai avant de libérer le mutex, alors aucune tâche ne peut acquérir la ressource dans une condition de concurrence.

C'est ainsi que fonctionne l' opérateur synchronized en Java avec les méthodes wait()et notify().

3.4 Verrouillage à double contrôle

Le verrouillage à double contrôle est un modèle de conception parallèle destiné à réduire les frais généraux liés à l'obtention d'un verrou.

Tout d'abord, la condition de blocage est vérifiée sans aucune synchronisation. Un thread tente d'acquérir un verrou uniquement si le résultat de la vérification indique qu'il doit acquérir le verrou.

//Double-Checked Locking
public final class Singleton {
private static Singleton instance; //Don't forget volatile modifier

public static Singleton getInstance() {
     if (instance == null) {                //Read

         synchronized (Singleton.class) {    //
             if (instance == null) {         //Read Write
                 instance = new Singleton(); //
             }
         }
     }
 }

Comment créer un objet singleton dans un environnement thread-safe ?

public static Singleton getInstance() {
   if (instance == null)
    instance = new Singleton();
}

Si vous créez un objet Singleton à partir de différents threads, il peut arriver que plusieurs objets soient créés en même temps, ce qui est inacceptable. Par conséquent, il est raisonnable d'envelopper la création d'objet dans une instruction synchronisée.

public static Singleton getInstance() {
    synchronized (Singleton.class) {
        if (instance == null)
        instance = new Singleton();
    }
}

Cette approche fonctionnera, mais elle a un petit inconvénient. Une fois l'objet créé, chaque fois que vous essayez de l'obtenir à l'avenir, une vérification sera effectuée dans le bloc synchronisé, ce qui signifie que le thread actuel et tout ce qui s'y rapporte seront verrouillés. Donc ce code peut être un peu optimisé :

public static Singleton getInstance() {
     if (instance != null)
        return instance;

    synchronized (Singleton.class) {
        if (instance == null)
        instance = new Singleton();
    }
}

Sur certains langages et/ou sur certaines machines, il n'est pas possible d'implémenter ce modèle en toute sécurité. Par conséquent, on l'appelle parfois un anti-modèle. De telles fonctionnalités ont conduit à l'apparition de la relation d'ordre strict "se produit avant" dans le modèle de mémoire Java et le modèle de mémoire C++.

Il est généralement utilisé pour réduire la surcharge liée à l'implémentation de l'initialisation différée dans les programmes multithreads, tels que le modèle de conception Singleton. Dans l'initialisation paresseuse d'une variable, l'initialisation est différée jusqu'à ce que la valeur de la variable soit nécessaire dans le calcul.

3.5 Planificateur

Le planificateur est un modèle de conception parallèle qui fournit un mécanisme pour implémenter une politique de planification, mais qui est indépendant de toute politique particulière. Contrôle l'ordre dans lequel les threads doivent exécuter le code séquentiel, à l'aide d'un objet qui spécifie explicitement la séquence des threads en attente.

Vous pouvez trouver plus de détails sur ce modèle dans la documentation officielle. Il y a aussi un excellent article en russe .