Accueil
Java Standard Edition
Java EE 5
Visual Basic .Net 2005
Visual C++ .Net 2005
Visual C# .Net 2005
Cours ASP .Net 2.0
Postgresql
Linux
Visual Studio 2008
ASP 3.0 Classique
Cours Javascript - DOM - DHTML
Cours Ajax
VBA
Assembleur
Perl
Membres
L'auteur du site
Nouveautés sur le site
Contacts
Plan du site
Exécution d'un programme
Les archives Jar
Les classes abstraites
Les interfaces
Les tableaux
La généricité
Les énumérations
Les classes internes
Classes anonymes et internes locales
Les threads: généralités
Les threads(2): synchronisation
E/S(1):InputStream et OutputStream
E/S(2):FileInputStream et FileOutputStream
E/S(3):Reader et Writer
E/S(4):FilterInputStream et FilterOutputStream
E/S(5):Les filtres d'octets: PrintStream
E/S(6):Les filtres d'octets: DataInputStream et DataOutputStream
E/S(7):Les filtres d'octets: BufferedInputStream et BufferedOutputStream
E/S(8):Flux de caractères: PrintWriter
E/S(9):Flux de caractères: FilterReader et FilterWriter
E/S(10):Flux de caractères: InputStreamReader, OutputStreamWriter, StreamDecoder, StreamEncoder
E/S(11):Flux de caractères: BufferedReader et BufferedWriter
E/S(12):Flux de caractères: FileReader et FileWriter
La classe String (java.lang)
Les collections: L'interface Collection(java.lang)
Les collections(2): L'interface List(java.util)
Les collections(3): AbstractCollection(java.util)
Les collections(4): AbstractList(java.util)
Les collections(5): Vector(java.util)
Les collections(6): L'interface Map(java.util)
Les collections(7): AbstractMap(java.util)
Les collections(8): HashMap(java.util)
La bibliothèque Swing en Java
Les bases de données en Java
JDBC ( Java Database Connectivity )
Les interfaces graphiques
Les fichiers de configuration en Java
INSTALLATION JAVA EE 5, JRE 6, ECLIPSE, TOMCAT, ETC SOUS LINUX
INSTALLATION JAVA EE 5, JRE 6, ECLIPSE, TOMCAT, ETC SOUS WINDOWS
Les applications Web en java
Les filtres Java (javax.servlet.Filter)
I Généralités
I.1 Le formulaire principal
I.2 Les objets créés par Visual
I.3 Les variables références
I.4 Le garbage collector
II Créer évènements
II.1 Rappel évènements
II.2 Procédure à suivre
II.2.1 Créer son EventArgs
II.2.2 Créer EmetEvent
II.2.3 Déclarations autres
I Généralités
I.1 Applications winforms
I.2 Applications MFC
I.3 Objets managés ou pas
I.4 Objets non managés
I.5 Objets managés - handle
I.6 Le top-level ^
II Créer évènements
II.1 Rappel évènements
II.2 Procédure à suivre
II.2.1 Créer son EventArgs
II.2.2 Créer EmetEvent
II.2.3 Déclarations autres
I Généralités
I.1 Puissant et Accessible
I.2 Créer ses classes
II Créer évènements
II.1 Rappel évènements
II.2 Procédure à suivre
III Les services Windows
IV Le .net remoting
V Communication Tcp avec TcpClient et TcpListener
II.2.1 Créer son EventArgs
II.2.2 Créer EmetEvent
II.2.3 Déclarations autres
I Généralités
I.1 Un EDI formidable
I.2 Inclure C# ou VB
I.3 L'objet Response
I.4 Les évènements
II ASP .net et les bdd
II.1 Essayer plusieurs fois la requête
I 2.1 Fichiers distincts
I.2.2 Avec la balise script
I.2.3 Inclure réellement
I.2.4 Avec Response.Write()
I.3.1 La méthode Response.Redirect()
I.4.1 Résoudre problème post
Installation Postgre Linux
Cours Postgresql
Le Shell Unix( Linux, Ubuntu)
Les scripts C-Shell
Programmation système Unix
Reseau Linux
Les iptables
Windows Presentation Foundation(WPF)
Le Framework 3.0
Windows Workflow Foundation(WF)
ASP 3.0 Classique
Cours Javascript - DOM - DHTML
Chat Ajax
VBA Excel 2003
Assembleur
Perl
Inscription
Liste membres
Livre d'or
Forum
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:
package banque; public class CompteBancaire { private int solde; //solde en euros public CompteBancaire(int solde) { super(); this.solde = solde; } //public synchronized void retirerCheque( int somme) {//version synchronisée public void retirerCheque( int somme) { //version non synchronisée int calcul = solde; //calcul prend la valeur du solde, à cet instant t System.out.println("Le solde initial est de " + calcul ); try { //on attend quelques secondes, pour simuler un traitement entre les deux, //et pour pouvoir bien voir l'effet gênant Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } calcul = calcul - somme; solde = calcul; //on affecte le nouveau solde afficherSolde(); } //public synchronized void retirerCarte( int somme ) { //version synchronisée public void retirerCarte( int somme ) { //version non synchronisée solde = solde - somme; afficherSolde(); } public void afficherSolde() { System.out.println("Le nouveau solde est de " + solde ); } }
La classe Magasin_MT:
package banque; public class Magasin_MT implements Runnable { private String nom; //nom du magasin private CompteBancaire compteDuClient; public Magasin_MT(String nom, CompteBancaire compteDuClient) { super(); this.nom = nom; this.compteDuClient = compteDuClient; } /** * Le run() exécute un code différent, suivant le magasin. * On a un run() "centralisé", car on a chaque instance de * la classe, qui veut exécuter un code différent. * Ici, le code est différent, car chaque magasin exécute une * opération d'exemple. On veut un exemple rapide à programmer. * Mais si ce n'était pas un exemple, * le code serait similaire à tous les magasins: exécuter les * opérations non traitées, etc... */ public void run() { if (nom.equalsIgnoreCase("magdupont")) { compteDuClient.retirerCheque(500); } else if (nom.equalsIgnoreCase("magdurand")) { compteDuClient.retirerCarte(240); } else { System.out.println("Ce magasin ne fait aucune opération sur le compte"); } } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } }
Et enfin la classe Main:
package main; import banque.CompteBancaire; import banque.Magasin_MT; public class Main { /** * @param args */ public static void main(String[] args) { CompteBancaire monCompte = new CompteBancaire(600); //on a 600 euros Magasin_MT magdupont = new Magasin_MT("magdupont", monCompte); Magasin_MT magdurand = new Magasin_MT("magdurand", monCompte); Thread thDupont = new Thread(magdupont); Thread thDurand = new Thread(magdurand); thDupont.start(); try { //on attend 1s avant de démarrer thDurand Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } thDurand.start(); } }
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:
public synchronized void retirerCheque( int somme) (...) public synchronized void retirerCarte( int somme ) (...)
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:
public synchronized void retirerCarte( int somme ) { //version synchronisée solde = solde - somme; afficherSolde(); }
Cela est équivalent à:
public void retirerCarte( int somme ) { //version synchronisée synchronized(this) { solde = solde - somme; afficherSolde(); } }
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:
public class CompteBancaire { private static int decouvert; //decouvert autorise pour ce compte bancaire private int solde; //solde en euros private Object monObjSynchro; public CompteBancaire(int solde) { super(); this.solde = solde; monObjSynchro = new Object(); } //Version de retirerCheque et retirerCarte, avec un autre objet de //synchronisation que this public void retirerCheque( int somme) { synchronized(monObjSynchro) { int calcul = solde; System.out.println("Le solde initial est de " + calcul ); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } calcul = calcul - somme; solde = calcul; afficherSolde(); } } public void retirerCarte( int somme ) { synchronized(monObjSynchro) { solde = solde - somme; afficherSolde(); } } (...)
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:
synchronized(this.getClass()) { nomClasse.varStatic = (...) (...) }
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)
package banque; /** * * @author sabri Koffler * */ public class CompteBancaire { private int solde; //solde en euros private Object monObjSynchro; public CompteBancaire(int solde) { super(); this.solde = solde; monObjSynchro = new Object(); } public synchronized void retirerCheque( int somme) {//version synchronisée //public void retirerCheque( int somme) { //version non synchronisée int calcul = solde; //calcul prend la valeur du solde, à cet instant t System.out.println("Le solde initial est de " + calcul ); try { //on attend quelques secondes, pour simuler un traitement entre les deux, //et pour pouvoir bien voir l'effet gênant Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } calcul = calcul - somme; solde = calcul; //on affecte le nouveau solde afficherSolde(); } public synchronized void retirerCarte( int somme ) { //version synchronisée //public void retirerCarte( int somme ) { //version non synchronisée solde = solde - somme; afficherSolde(); } /*//Version de retirerCheque et retirerCarte, avec un autre objet de //synchronisation que this public void retirerCheque( int somme) { synchronized(monObjSynchro) { int calcul = solde; System.out.println("Le solde initial est de " + calcul ); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } calcul = calcul - somme; solde = calcul; afficherSolde(); } } public void retirerCarte( int somme ) { synchronized(monObjSynchro) { solde = solde - somme; afficherSolde(); } } */ public void afficherSolde() { System.out.println("Le nouveau solde est de " + solde ); } public static synchronized void augmenterDecouvert() { } }
VIII-2) La classe Magasin_MT (package banque)
package banque; /** * Classe Magasin Multi-thread * @author sabri Koffler * */ public class Magasin_MT implements Runnable { private String nom; //nom du magasin private CompteBancaire compteDuClient; public Magasin_MT(String nom, CompteBancaire compteDuClient) { super(); this.nom = nom; this.compteDuClient = compteDuClient; } /** * Le run() exécute un code différent, suivant le magasin. * On a un run() "centralisé", car on a chaque instance de * la classe, qui veut exécuter un code différent. * Ici, le code est différent, car chaque magasin exécute une * opération d'exemple. On veut un exemple rapide à programmer. * Mais si ce n'était pas un exemple, * le code serait similaire à tous les magasins: exécuter les * opérations non traitées, etc... */ public void run() { if (nom.equalsIgnoreCase("magdupont")) { compteDuClient.retirerCheque(500); } else if (nom.equalsIgnoreCase("magdurand")) { compteDuClient.retirerCarte(240); } else { System.out.println("Ce magasin ne fait aucune opération sur le compte"); } } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } }
VIII-3) La classe Main (package main)
package main; import banque.CompteBancaire; import banque.Magasin_MT; /** * * @author sabri Koffler pour www.infkoffler.com, exemple du cours Java Standard * @version 1.0 (de l'application) / 28/12/2008 * Créé 28/12/2008 * Programme d'exemple pour les threads(2): la synchronisation * */ public class Main { /** * @param args */ public static void main(String[] args) { CompteBancaire monCompte = new CompteBancaire(600); //on a 600 euros Magasin_MT magdupont = new Magasin_MT("magdupont", monCompte); Magasin_MT magdurand = new Magasin_MT("magdurand", monCompte); Thread thDupont = new Thread(magdupont); Thread thDurand = new Thread(magdurand); thDupont.start(); try { //on attend 1s avant de démarrer thDurand Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } thDurand.start(); } }
IX) Téléchargement de l'exemple
Projet d'exemple, V1.0
RETOUR HAUT