Synchronisation des threads en Java
Lorsque nous démarrons deux ou plusieurs threads dans un programme, il peut arriver que plusieurs threads essaient d'accéder à la même ressource et qu'ils puissent finalement produire des résultats imprévus en raison de problèmes de concurrence. Par exemple, si plusieurs threads essaient d'écrire dans le même fichier, ils risquent de corrompre les données, car l'un des threads peut remplacer les données ou lorsqu'un thread ouvre le même fichier en même temps qu'un autre peut fermer le même fichier.
Il est donc nécessaire de synchroniser l'action de plusieurs threads et de vous assurer qu'un seul thread peut accéder à la ressource à un moment donné. Ceci est mis en œuvre en utilisant un concept appelé moniteurs. Chaque objet en Java est associé à un moniteur, qu'un thread peut verrouiller ou déverrouiller. Un seul thread à la fois peut verrouiller un moniteur.
Le langage de programmation Java offre un moyen très pratique de créer des threads et de synchroniser leur tâche à l’aide de blocs synchronized. Vous conservez des ressources partagées dans ce bloc.
Syntaxe :
synchronized(objectidentifier) { // Accéder aux variables partagées et autres ressources partagées }
Ici Objectidentifier est une référence à un objet dont le verrou est associé au moniteur représenté par l'instruction synchronized.
Exemple 1 :
Voici un exemple simple qui peut ou non d'afficher la valeur du compteur en séquence et chaque fois que nous l'exécutons, il produit un résultat différent en fonction de la disponibilité du processeur pour un thread.
class Compteur { public void afficher() { try { for (int i = 10; i >= 0; i--) { System.out.println("i = " + i); } } catch (Exception e) { System.out.println("Thread interrompu."); } } } class Multithreading implements Runnable { Compteur cpt; public Multithreading(Compteur cpt) { this.cpt = cpt; } public void run() { cpt.afficher(); System.out.println("Bye bye " + Thread.currentThread().getName()); } } // classe principale public class Multithread { public static void main(String[] args) { Compteur cpt = new Compteur(); Thread obj1 = new Thread(new Multithreading(cpt)); Thread obj2 = new Thread(new Multithreading(cpt)); obj1.start(); obj2.start(); // attendre la fin des threads try { obj1.join(); obj1.join(); } catch (Exception e) { } } }
i = 4
i = 3
i = 3
i = 2
i = 2
i = 1
i = 1
i = 0
i = 0
Bye bye Thread-1
Bye bye Thread-0
Exemple 2 :
Voici le même exemple qui affiche la valeur du compteur en séquence et chaque fois que nous l’exécutons, le résultat est identique.
class Compteur { public void afficher() { try { for (int i = 4; i >= 0; i--) { System.out.println("i = " + i); } } catch (Exception e) { System.out.println("Thread interrompu."); } } } class Multithreading implements Runnable { Compteur cpt; public Multithreading(Compteur cpt) { this.cpt = cpt; } public void run() { synchronized (cpt) { cpt.afficher(); } System.out.println("Bye bye " + Thread.currentThread().getName()); } } // classe principale public class Multithread { public static void main(String[] args) { Compteur cpt = new Compteur(); Thread obj1 = new Thread(new Multithreading(cpt)); Thread obj2 = new Thread(new Multithreading(cpt)); obj1.start(); obj2.start(); // attendre la fin des threads try { obj1.join(); obj1.join(); } catch (Exception e) { } } }
i = 3
i = 2
i = 1
i = 0
i = 4
i = 3
i = 2
i = 1
i = 0
Bye bye Thread-1
Bye bye Thread-0
Dans l'exemple ci-dessus, nous avons choisi de synchroniser l'objet Compteur dans la méthode run() de la classe Multithreading. Alternativement, nous pourrions définir l'ensemble du bloc de la méthode afficher() comme étant synchronisé et produire le même résultat.
Les points importants
- Lorsqu'un thread entre dans une méthode ou un bloc synchronisé, il acquiert un verrou et une fois qu'il a terminé sa tâche et qu'il quitte la méthode synchronisée, il libère le verrou.
- Lorsque le thread entre dans une méthode ou un bloc d'instance synchronisée, il acquiert un verrou de niveau objet et lorsqu'il entre dans une méthode ou un bloc statique synchronisé, il acquiert un verrou de niveau de classe.
- La synchronisation Java lève une exception NullPointerException si l'objet utilisé dans un bloc synchronisé est nul. Par exemple, si dans synchronized (instance), instance est null, elle lève une exception NullPointerException.
- En Java, wait(), notify() et notifyAll() sont les méthodes importantes utilisées dans la synchronisation.
- Vous ne pouvez pas appliquer le mot clé java synchronized aux variables.
- Ne synchronisez pas sur le champ non final du bloc synchronisé, car la référence au champ non final peut changer à tout moment et différents threads peuvent alors se synchroniser sur différents objets, c’est-à-dire qu’il n’ya aucune synchronisation.
Avantages de la Synchronisation
- Multithreading: comme Java est un langage multithread, la synchronisation est un bon moyen de parvenir à une exclusion mutuelle sur les ressources partagées.
- Méthodes d'instance et statiques: les méthodes d'instance synchronisées et les méthodes statiques synchronisées peuvent être exécutées simultanément car elles sont utilisées pour verrouiller différents objets.
Limites de la Synchronisation
- Limitations de la simultanéité: la synchronisation Java n'autorise pas les lectures simultanées.
- Diminution de l'efficacité: la méthode synchronized s'exécute très lentement et peut dégrader les performances. Vous devez donc synchroniser la méthode lorsque cela est absolument nécessaire et ne synchroniser le bloc que pour la section critique du code.