Sécurité des threads et utilisation du mot-clé synchronized en Java

Sécurité des threads

Les problèmes de sécurité des threads surviennent pour plusieurs raisons fondamentales.

Cause racine : ordonnancement aléatoire des threads

L'ordonnancement des threads par le système d'exploitation est imprévisible, ce qui peut condurie à des comportements inattendus.

Modification de données partagées

La modification simultanée d'une même variable par plusieurs threads pose des risques. En revanche, la lecture par plusieurs threads ou la modification de variables distinctes est généralement sans danger. Voici un exemple illustrant le problème :

// Exemple de compteur non synchronisé
class CompteurNonProtege {
    private int valeur = 0;
    public void incrémenter() { valeur++; } // Opération non atomique
}

Opérations non atomiques

Une opération atomique est indivisible : elle réussit complètement ou échoue sans état intermédiaire. Les opérations simples comme l'affectation (=) en Java sont atomiques. Cependant, des instructions apparentes comme valeur++ impliquent des étapes multiples (lecture, modification, écriture), ce qui peut entraîner des pertes de mises à jour en environnement multithreadé.

Problèmes de visibilité

En raison des caches CPU et des optimisations compilateur, les modifications d'un thread peuvent ne pas être immédiatement visibles par d'autres sans mécanismes de synchronisation. L'exemple suivant montre une boucle infinie possible :

class ProblèmeVisibilité {
    private boolean indicateur = false; // Manque de volatile
    public void basculer() { indicateur = true; }
    public void exécuter() { while(!indicateur); } // Peut ne jamais s'arrêter
}

Réordonnancement des instructions

Les compilateurs et processeurs peuvent réordonner les instructions pour optimiser les performances, ce qui peut briser des hypothèses temporelles implicites. Cela pose des risques en multithreadé, contrairement au single-thread.

Le mot-clé synchronized

Exclusion mutuelle

Pour résoudre les problèmes de sécurité, on transforme les opérations non atomiques en opérations atomiques en utilisant des verrous. Le mot-clé synchronized encapsule une section de code pour garantir l'atomicité. L'acquisition du verrou se fait à l'entrée, et la libération à la sortie. Lorsque plusieurs threads s'affrontent pour un même verrou, le premier à l'acquérir bloque les autres, qui restent en attente jusqu'à sa libération.

Réentrance

Les blocs synchronized sont réentrants pour un même thread, évitant ainsi les interblocages. Par exemple, imbriquer deux verrous sur le même objet fonctionne correctement :

for (int i = 0; i < 50000; i++) {
    synchronized (verrou) {
        synchronized (verrou) {
            compteur++;
        }
    }
}

En Java, synchronized est un verrou réentrant, ce qui empêche les situations d'interblocage dans ce cas.

Utilisation de synchronized

Méthode de base :

synchronized (objetVerrou) {
    // Code à protéger
}

Application à une méthode non statique : Cette forme est équivalente à synchroniser sur this.

Application à une méthode statique : Ici, le verrou est appliqué sur l'objet de classe, comme ClasseCompteur.class.

Concepts essentiels

Pour manipuler des objets de classe comme ClasseCompteur.class, on utilise la réflexion. En Java, à l'exécution, on peut accéder aux métadonnées d'une classe ou d'un objet, telles que ses membres, méthodes, hiérarchie d'héritage, etc. Les fichiers .java compilent en .class, qui sont chargés en mémoire par la JVM pour obtenir les objets de classe. Un point crucial est que pour éviter les conflits de verrouillage, il faut que les threads synchronisent sur le même objet, peu importe lequel, tant que c'est identique.

Étiquettes: Java multithreading synchronized Thread Safety Concurrency

Publié le 30 juin à 22h49