Skip Navigation Links
Accueil
Java Standard EditionExpand Java Standard Edition
Java EE 5Expand Java EE 5
Visual Basic .Net 2005Expand Visual Basic .Net 2005
Visual C++ .Net 2005Expand Visual C++ .Net 2005
Visual C# .Net 2005Expand Visual C# .Net 2005
Cours ASP .Net 2.0Expand Cours ASP .Net 2.0
PostgresqlExpand Postgresql
LinuxExpand Linux
Visual Studio 2008Expand Visual Studio 2008
ASP 3.0 ClassiqueExpand ASP 3.0 Classique
Cours Javascript - DOM - DHTMLExpand Cours Javascript - DOM - DHTML
Cours AjaxExpand Cours Ajax
VBAExpand VBA
AssembleurExpand Assembleur
PerlExpand Perl
MembresExpand Membres
L'auteur du site
Nouveautés sur le site
Contacts
Plan du site
Accueil > Java Standard Edition > Les threads(2): synchronisation
____________________________________________________________________________________________________
Connexion

JSE - le langage - Les threads(2): synchronisation de threads

Sommaire :

I) Exemple de problème dû à un manque de synchronisation
II) La synchronisation
III) Synchronisation d'un bloc
IV) Utiliser un autre objet de synchronisation que this
V) La synchronisation des variables de classe( et des méthodes static)
VI) Performances et synchronisation
VII)Le vocabulaire habituel en programmation concurrente
   VII-1) Section critique
   VII-2) Objet moniteur("object monitor")
   VII-3)Définition théorique d'un moniteur, et adaptation aux moniteurs en java
VIII)Code source complet de l'exemple
   VIII-1) La classe CompteBancaire (package banque)
   VIII-2) La classe Magasin_MT (package banque)
   VIII-3) La classe Main (package main)
IX) Téléchargement de l'exemple

I) Exemple de problème dû à un manque de synchronisation

Comme nous avons vu dans le premier chapitre sur les threads, les problèmes surviennent à partir du moment où les threads partagent des ressources communes.

Prenons l'exemple classique d'un compte bancaire d'un particulier.
Un magasin A retire son cheque de 500 euros. Et, au même moment, un magasin B retire un montant carte bancaire de 240 euros.

La classe CompteBancaire: La classe Magasin_MT: Et enfin la classe Main: Résultat affiché:
"Le solde initial est de 600
Le nouveau solde est de 360
Le nouveau solde est de 100"

Au départ, le solde est de 600 euros. magdurand retire 1 seconde après le départ, pendant que magdupont traite encore. magdupont fait passer le solde à 360.
Puis, 1s après, magdurand a fini de traiter: il fait passer le solde à 100 (600-500), au lieu de -140 (360-500). La valeur du solde sur laquelle il s'est basé, a été modifiée entre temps. Elle n'était pas à jour.
Il aurait fallu interdire à magdupont de modifier le solde, jusqu'à temps que magdurand ait fini son traitement( sa transaction)
La ressource commune aux deux threads, ici, est le solde.

Si les deux threads ne faisaient que lire la ressource commune, cela ne poserait aucun problème. Le problème est venu du fait que les deux threads modifient cette ressource: il y a modifications concurrentes. Si magdupont( le 2eme thread), ne faisait que lire le solde, cela n'aurait pas posé de problème: il aurait lu le solde pas encore modifié par magdurand. Et ceci aurait été normal, car magdurand n'avait pas terminé son traitement.

II) La synchronisation

Dans l'exemple précédent, il aurait fallu qu'on interdise à magdupont de modifier le solde , tant que magdurand n'avait pas terminé sont traitement.
Ceci est possible avec l'instruction synchronized.

Maintenant, décommentez les deux lignes avec synchronized, dans CompteBancaire:
On a uniquement rajouté le synchronized.

Ceci à pour effet que dès qu'une méthode synchronized, d'une certaine instance d'une classe, commence, une autre méthode synchronized, de la même instance de cette classe, ne peut pas commencer.
La deuxième méthode doit attendre que la première se termine.

Regardons le résultat ici, qui est correct:
Le solde initial est de 600
Le nouveau solde est de 100
Le nouveau solde est de -140

Le résultat est à présent correct. Remarquons que le "100"(résultat de magdurand) s'affiche au bout de deux secondes(ce qui est normal). Le résultat de magdupont, qui devait s'afficher au bout de 1s, a dû attendre la fin du traitement de magdurand. Ceci explique qu'il affiche le -140 au bout des 2s, et aussi après l'affichage du résultat de magdurand.

- Dans notre exemple de synchronisation des magasins, la deuxième méthode a dû attendre la première. Elle a dû attendre une seconde de plus que prévu. Elle est devenu dépendante de la première méthode au niveau de la durée de celle-ci, mais aussi en cas de blocage, elle est bloquée aussi.

D'autre part, la méthode 2 attend la fin de la méthode 1, pour pouvoir commencer: c'est bien une synchronisation en temps.

- Le synchronized, pour une méthode, synchronise cette méthode sur l'instance courante de l'objet.
Chaque instance d'un objet( java) possède un verrou associé. Lorsque un thread entre dans une méthode synchronized, il verrouille l'objet. Et le verrou est enlevé à la fin de la méthode.
Que veut dire qu'un objet est verrouillé? Cela veut dire qu'un bloc synchronisé sur cet objet, attendra qu'il soit déverrouillé, pour s'exécuter. Mais un bloc non synchronisé pourra y accéder: il faut synchroniser toutes les méthodes de l'objet( sauf celles qui ne sont pas concernées par les ressources communes, bien sûr).

III) Synchronisation d'un bloc

Jusqu'à présent, nous avons vu la synchronisation de méthodes. Il est aussi possible de synchroniser un bloc de code. Ce n'est que généraliser ce qu'on vient de faire, car une méthode n'est qu'un bloc de code. Prenons pour exemple la méthode retirerCarte de CompteBancaire: Cela est équivalent à: L'instruction synchronized est suivie de l'objet sur lequel s'effectue la synchronisation, en général this. On peut synchroniser uniquement les blocs concernés. Ceci permet d'augmenter les performances, quand les méthodes synchronized sont trop longues.

IV) Utiliser un autre objet de synchronisation que this

Les ressources communes, sont dans l'objet sur lequel on synchronise. Et ce sont des attributs private, pour ne pas qu'on les modifie de manière non synchronisée. En fait, on aurait pu donner n'importe quel objet de synchronisation, du moment que toutes les méthodes qui se synchronisent entres elles, utilisent le même objet. Mais il est plus simple de prendre l'objet qui contient les ressources communes, car le programme sera très lisible alors. Car on saura immédiatement quelles sont les ressources protégées, rien qu'en voyant l'objet de synchronisation.

Donc, en prenant comme objet de synchronisation d'une méthode, l'objet contenant les ressources communes concernées par cette méthode(this dans notre exemple), toutes les méthodes ayant les mêmes ressources communes, auront le même objet de synchronisation. Ces méthodes ayant le même objet de synchronisation, elles vont donc se synchroniser entres elles. Or c'est bien ce qu'on voulait: c'est que toutes les méthodes qui partagent les mêmes ressources, se synchronisent entres elles.
Choisir le this, permet aussi d'éviter d'allouer spécialement un objet pour synchroniser, car on l'a déjà!

Dans le projet d'exemple, figure une version de nos deux méthodes, mais en utilisant un autre objet de synchronisation que le this: Remarquez que l'objet de synchronisation est mis en attribut privé, et qu'on l'instancie par un new Object(), dans le constructeur.

Le résultat de l'exécution est le même, ce qui prouve bien que les deux méthodes ont été synchronisées entres elles:

Le solde initial est de 600
Le nouveau solde est de 100
Le nouveau solde est de -140

Remarque: ceci prouve qu'il n'y a pas d'idée de blocage de l'objet de synchronisation, car les ressources communes ici ne sont pas dans l'objet de synchronisation. Et la synchronisation a lieu malgré tout.

V) La synchronisation des variables de classe( et des méthodes static)

Lorsque les ressources communes sont les propriétés statiques d'une classe, on synchronise sur l'objet Class de cette classe, qui la représente. Cet objet peut être obtenu avec la méthode getClass des instances de cette classe. Il est instancié au démarrage de la JVM.
Le bloc sera synchronisé ainsi: Le verrou est posé sur l'objet Class, et non plus sur l'instance this de l'objet.
Ceci se comprend aisément, car les ressources communes sont ici propres à une classe, et non plus à une instance particulière de cette classe. Il est logique donc d'utiliser cet objet.
De plus, c'est un objet qui sera accessible dans toutes les instances de cette classe.
Dans n'importe quelle méthode d'une instance quelconque, on pourra faire un synchronized sur cet objet.

Les méthodes static sont également synchronisées sur l'objet Class, de la même manière que les méthodes d'instance sont synchronisées avec le this.
En effet, les méthodes static ne peuvent manipuler des attributs d'instance, donc les ressources communes utilisées par ces méthodes ne peuvent être que des attributs de classe(static). Il est donc logique que java synchronise les méthodes static sur l'objet Class.

VI) Performances et synchronisation

Les méthodes synchronisées sont bien plus lentes que les méthodes non synchronisées, car il faut vérifier le verrou. Il faut donc réfléchir à l'emploi du synchronized.

VII)Le vocabulaire habituel en programmation concurrente

Essayons de faire le rapprochement avec le vocabulaire habituel existant pour ce type de programmation.
Cette façon de synchroniser, avec le synchronized, est de type moniteur.

VII-1) Section critique

Ce sont les parties délicates, celles qui posent des problèmes dûes à l'utilisation de ressources communes. "synchronized" permet de définir les sections critiques.

VII-2) Objet moniteur("object monitor")

java.sun.com: "The Java programming language uses monitors to synchronize threads. Each object is associated with a monitor, which can also be referred as an object monitor. If a thread invokes a synchronized method on an object, that object is locked. Another thread invoking a synchronized method on the same object will block until the lock is released. Besides the built-in synchronization support, the java.util.concurrent.locks package that was introduced in J2SE 5.0 provides a framework for locking and waiting for conditions. Deadlocks can involve object monitors as well as java.util.concurrent locks."

TRADUCTION PERSONNELLE SOMMAIRE
Ce sont des moniteurs qui sont utilisés pour la synchronisation en java. A chaque objet est associé un moniteur, qu'on peut aussi appeler objet moniteur("object monitor").
FIN TRADUCTION

Le site de sun utilise "object monitor", quand il parle de ce moniteur. On précise ainsi que le moniteur est ici un objet, et n'est pas un programme. C'est bien un moniteur, car son rôle est de synchroniser les sections critiques. Mais c'est juste un objet: le verrou associé à chaque objet.

"La mise en place du moniteur, l'attente et le reveil des threads est fait automatiquement par Java."( laboratoire PRISM): la création de l'object monitor n'est pas à la charge du développeur. Et l'attente du thread en cas de synchronized "occupé", ou son réveil quand il n'est plus occupé, n'est pas non plus à la charge du programmeur.

"En Java, chaque objet possède un moniteur qui peut garder les sections critiques.(...).accès au moniteur : synchronized"( Université de Montréal).
"trois possibilités :
– synchronized (objet) { instr } moniteur associé à objet
– synchronized void Methode(){....} moniteur associé à this
– static synchronized void Methode(){...} moniteur associé
à la classe actions de bas niveau : lock et unlock"( Université de Montréal).
Il y a trois types de moniteurs(qui, en réalité, sont tous des moniteurs associés à un objet): moniteur associé à un objet(dans le cas de la syntaxe synchronized(objet)), moniteur associé à this(dans le cas des méthodes synchonized), et moniteur associé à la classe(dans le cas des méthodes synchronized statiques).

VII-3)Définition théorique d'un moniteur, et adaptation aux moniteurs en java

Reportons-nous au livre "Les systèmes d'exploitation" de Samia Bouzefrane:
"Un moniteur est un objet abstrait réalisant la synchronisation entre processus. Il est composé des éléments suivants:

- déclaration des variables locales représentant la ressource partagée
- déclaration et corps des procédures accédant les variables locales
- initialisation des variables locales( cette initialisation est exécutée à la création du moniteur).

Le programme qui utilise le moniteur ne peut pas accéder directement aux variables locales; il ne peut qu'invoquer les noms des procédures du moniteur, celles qui ne sont visibles qu'à l'extérieur, le moniteur pouvant avoir des procédures locales qui ne sont pas accessibles directement des programmes.

Par définition, les procédures du moniteur sont en exclusion mutuelle, exclusion dont la réalisation est à la charge du compilateur et non plus du programmeur. Ainsi donc, un seul processus à la fois peut être actif à l'intérieur du moniteur, c'est-à-dire en cours d'exécution de l'une des procédures du moniteur."(Samia Bouzefrane).

Nous nous apercevons que cette définition correspond tout-à-fait à notre classe contenant les synchronized.

Remarque sur le concept de moniteur:
Cela paraît compréhensible d'appeler moniteur, la classe concernée, car elle contient le code de synchronisation (les synchronized), donc de pilotage du thread(lui permettre d'exécuter le traitement ou le faire attendre jusqu'à ce qu'il en ait le droit). Ceci dit, cette classe contient également, outre le rôle de pilotage du thread, le code des traitements à effectuer. Cela peut un peu surprendre, car par moniteur, on s'attendrait plutôt à un processus uniquement de pilotage. Mais cela reste un processus de pilotage, qui contient, de plus, les traitements à surveiller.
Remarque: Avec cette appellation, on parlerait d'utilisation de l'objet moniteur, par le moniteur.

Voici une citation en accord avec la nomination de moniteur, de la classe contenant les ressources communes et les méthodes synchronized:
"Un moniteur sera représenté par une classe, contenant toutes les variables d'état. Le moniteur possède une sémantique : pour deux mécanismes de synchronisation différents, il faut deux moniteurs différents (deux objets moniteurs différents, pas forcément deux classes différentes ; si on doit gérer par exemple deux imprimantes de même type, la même classe peut convenir).

Un moniteur possède des points d'accès qui modifient les variables d'état. Ces points d'accès seront représentés par des méthodes.

L'appel d'un point d'accès provoque automatiquement l'exclusion mutuelle. Pour réaliser cela, toutes les méthodes d'un moniteur Java seront synchronized.

Enfin, le moniteur protège ses variables, qui ne sont modifiables que par l'appel des points d'accès. Pour cela, les variables du moniteur Java seront private.

Résumé

On appellera Moniteur Java une classe dont :

toutes les variables sont private ;
toutes les méthodes sont synchronized.
Le respect de ces règles n'est pas une nécessité absolue. Mais il garantit un bon fonvtionnement du programme, ce qui les justifie."(université de la Méditerranée).

VIII)Code source complet de l'exemple

VIII-1) La classe CompteBancaire (package banque)

VIII-2) La classe Magasin_MT (package banque)

VIII-3) La classe Main (package main)

IX) Téléchargement de l'exemple

Projet d'exemple, V1.0

RETOUR HAUT