TD/TP Miage3, 2005-2006 T D / TP M I A G E 3 , T H R E A D S J A V A L’objectif de ce TD/TP est de vous faire programmer des threads en Java, de vous faire observer le comportement des programmes à activités parallèles (multi-threadés) et de vous montrer le besoin de synchronisation entre threads. 1 IMPLÉMENTATION DES THREADS EN JAVA Les threads Java peuvent être implémentés de deux manières. La première est d’étendre la classe prédéfinie Thread: //classes et interfaces prédéfinies Java, JDK interface Runnable { void run(); } public class Thread extends Object implements Runnable { void run() {...} void start() {...} ... } public class Compteur extends Thread { public void run() {...} } public static main() { Compteur c = new Compteur(); c.start(); } } L’héritage à partir de Thread est contraignant car il empêche tout autre héritage. Deuxième manière de faire: implémenter l’iterface Runnable et de cette manière avoir la possibilité d’héritage et d’implémentation d’autres interfaces. .//classes et interfaces prédéfinies Java, JDK interface Runnable { void run(); } public class Thread extends Object implements Runnable { void run() {...} void start() {...} ... } public class Compteur implements Runnable{ public void run() {...} } public static main() { Compteur c = new Compteur(); new Thread(c).start();} } 1 TD/TP Miage3, 2005-2006 2 EXERCICES Les programmes Java pour ce TD/TP se trouvent dans le placard electronique. http://www-ufrima.imag.fr/placard/INFO/miage3/SR/TDTP1 2.1 PROGRAMME EXEMPLETHREAD1.JAVA Compiler et exécuter le programme exempleThread1.java (pour exécuter plusieurs fois, il est possible d’utiliser exec.sh qui est fourni) Le programme crée trois threads qui affichent respectivement “Hello” “World” et “and Everybody”. Que pouvez-vous dire à propos de l’ordre de l’affichage? Expliquer. Le programme utilise le héritage de Thread pour coder les threads. Changer le code pour utiliser l’interface Runnable. 2.2 PROGRAMME EXEMPLETHREAD2.JAVA Compiler et exécuter plusieurs fois le programme exempleThread2. Voyez-vous tous les affichages? Essayez d’augmenter les valeurs d’attente (le arguments passés lors de la création des threads qui sont utilisés pour les appels à sleep). Si vous mettez de trop grandes valeurs, vous risquez de ne plus voir aucun affichage. Pourquoi? 2.3 EXEMPLETHREAD3.JAVA Dans ce programme, nous rajoutons la fonction join qui force le programme principal d’attendre la terminaison des threads. Cimpiler le programme, exécuter plusieurs fois et s’assurer que tous les affichages sont présents. 2.4 PROGRAMME MULTI-TÂCHE SIMPLE En s’inspirant des exemples précédents, ecrire une classe Compte pour la gestion de comptes. Définir des méthodes pour créditer, débiter et consulter le compte. Ecrire également une classe de thread CompteModifier (en utilisant l’interface Runnable) qui prend en paramètre un compte et qui modifie son solde. Ecrire un programme principal qui crée un compte, qui crée plusieurs threads CompteModifier en leur passant en paramètre le compte créé. Tester, exécuter, observer. Le programme a-t-il le comportement souhaité? 2.5 EXEMPLETHREAD4.JAVA Dans ce programme, nous manipulons une variable partagée de type Compte. On peut déposer de l’argent sur le compte ou retirer de l’argent. Le programme principal crée 20 threads ThreadDeposer qui déposent la même somme (10000, puisqu’ils deposent 1000 fois 2 TD/TP Miage3, 2005-2006 10), ainsi que 20 threads ThreadRetirer qui retirent cette même somme. Le résultat à la fin (compte.consulter()) doit donner 0. Compiler et exécuter plusieurs fois le programme (en utilisant exec.sh). Y a-t-il des cas où le résultat est différent? Expliquer. 2.6 EXEMPLETHREAD5.JAVA Pour corriger le problème dans l’exemple précedent, on définit les deux méthodes deposer et retirer en tant que synchronized. Compiler et exécuter le programme. S’assurer que le comportement est correct. 3 TD/TP Miage3, 2005-2006 ANNEXE : RAPPELS SUR LA PROGRAMMATION JAVA 2.7 CLASSES, INSTANCIATION, OBJETS, MAIN Java est un langage pour la programmation orientée-objet (OO). La programmation par objets permet de structurer les programmes non en termes de fichiers, de modules, de fonctions et de procédures, mais en termes de structures (les objets) qui modélisent les choses du monde réel. Exemple : vous voulez modéliser une voiture, vous allez définir une structure “Voiture”. La voiture a quatre roues, un volant, quatre portes, deux sièges avant, deux sièges arrière, etc : vous allez définir, à l’intérieur de la structure “Voiture” d’autres structures pour les différentes parties de la voiture. La voiture peut démarrer, s’arrêter, prendre de l’essence, avoir un accident : vous aller définir des actions pour manipuler la structure “Voiture”. Dans votre programme vous allez vouloir gérer différentes voitures (vous êtes vendeur de voitures), alors il va falloir pouvoir créer et manipuler différentes structures “Voiture”. Pour faire ceci en Java, nous allons définir une classe Voiture qui va définir la structure générique d’une voiture. Dans cette classe, nous allons définir des attributs pour dire quelles sont les parties d’une voiture. Nous allons également définir des méthodes pour les différentes actions que nous pouvons faire avec une voiture. Ce qui nous donne : class Voiture { //attributs Roue roueAvG, roueAvD, roueArG, roueArD; Volant volant; Siege sieges[4]; String marque; int nbKilometres; int litresEssence; String étatVoiture; ... //méthodes void démarrer() {étatVoiture=”démarré”;} void arrêter() {étatVoiture=”arrêté”;} ... } Le programme principal (celui qui est exécuté lorsqu’on lance le programme Java), est contenu dans une méthode main, définie également dans une classe. Par exemple, si nous avons un magasin de voitures, nous pouvons définir une classe Magasin. Dans cette classe Magasin nous voyons que la création de voitures se fait à l’aide d’une opération spéciale : new. Avec cette opération on appelle une méthode spéciale de la classe Voiture, son constructeur. Si on peut dire que la classe Voiture définit un type (qqchose d’abstrait), quand on appelle son constructeur nous créons un objet concret qui peut être manipulé dans le programme. On dit que la classe est instanciée. class Magasin { //attributs Voiture[100] stock; int nbVoitures; ... 4 TD/TP Miage3, 2005-2006 //méthodes void recevoirVoiture(int kilometres, int essence, String marque) { nbVoitures++; Voiture nouvelle = new Voiture (kilometres, essence, marque); stock[nbVoitures]=nouvelle; } void vendreVoiture(int numero) { for (int i = numero; i < nbVoitures-1; i++) stock[i]=stock[i+1]; nbVoitures--; } ... //programme principal void main() { while true() { System.out.println(“Menu principal\n“+ “1 : recevoir Voiture\n” + “2 : vendre Voiture\n”+ ...); ... //création de voiture if (choix==1) { ... recevoirVoiture(...); } } } Le constructeur dans la classe Voiture est défini comme suit: class Voiture { String marque; int nbKilometres; int litresEssence; ... Voiture (int k, int e, String m) { marque = m; nbKilometres = k; litresEssence=e; } ... } 2.8 COMPILATION ET EXÉCUTION Pour compiler notre programme Java: javac Voiture.java Magasin.java Pour exécuter: java Magasin //la classe qui contient le main On peut compiler les classes Java séparemment. Dans ce cas, il faut indiquer au compilateur où il peut trouver les classes qui sont référencées dans les classes que l’on est en train de compiler. Pour cela,on utilise le classpath. 5 TD/TP Miage3, 2005-2006 Par exemple: javac Voiture.java //crée Voiture.class ds répértoire courant javac -classpath . Magasin.java Pour créer les classes non dans le répertoire courant mais dans un répértoire destination: avac -d dest Voiture.java //crée Voiture.class ds dest javac -classpath destination Magasin.java La compilation d’une classe Java ne génère pas de langage machine (compréhensible par le processeur de la machine sur laquelle on travaille) mais crée un format intermédiare qui est interprête avant d’être exécuté sur le processeur. L’interprétation est faite par la machine virtuelle Java (JVM). L’avantage est que tout programme Java peut être exécuté sur les machines où est installée la machine virtuelle (portabilité et indépendance du matériel). Toutefois, si un nouveau matériel doit être pris en compte, la machine virtuelle doit être réécrite. Source .java javac compilation bytecode JVM Machine physique 2.9 AUTRES 2.9.1. Héritage Imaginons que le magasin ne vend pas uniquement des voitures, mais également des cmaions. Dans ce cas on voudrait avoir une entité générique Vehicule (on aura toujours quatre roues, une marque, avoir les mêmes actions). Par contre pour les véhicules on voudrait spécifier PermisB, alors que pour les camions PermisC. class Vehicule { Roue ... Volant int nbKilometres; int litresEssence String marque void démarrer()... void arrêter() ... } class Voiture extends Vehicule { String permis; Voiture(...) { super(); permis=”B”; } } class Camion extends Vehicule { 6 TD/TP Miage3, 2005-2006 String permis; Voiture(...) { super(); permis=”C”; } } La class Voiture hérite de Vehicule, ce qui veut dire qu’elle dispose des attributs et des méthodes définis dans Vehicule (réutilisation de code). 2.9.2. Accès aux attributs et aux méthodes Specifier Class Package Subclass World private Y N N N no specifier Y Y N N protected Y Y Y N public Y Y Y Y 2.9.3. static Les attributs et les méthodes static caractérisent les classes Java et non les objets. Ils sont utilisés en mettant le nom de la classe, point, le nom de la méthode /de l’attribut public class HelloWorld { //calcul de la moyenne de valeurs entières public static sayHelloWorld(String person) { System.out.println(pesron + «says Hello World!»); } public class MainCalss { public static void main() { HelloWorld.sayHelloWorld(«Olivier»); } } 2.9.4. Interfaces Mêmes fonctions, différentes implémentations. interface SommeProduit{ int somme(); int produit(); } class Doublet implements SommeProduit{ int a, b; ... public int somme(){return a + b;} public int produit(){return a * b;} } class Triplet implements SommeProduit{ int a, b, c; ... 7 TD/TP Miage3, 2005-2006 public int somme(){return a + b +c;} public int produit(){return a * b *c); 2.10 EXCEPTIONS Les exceptions servent à traiter les erreurs en Java. Dans le JDK (Java Development Kit), les classes prédéfinies viennent avec une définition des erreurs possibles i.e avec des execptions prédéfinies. Pour cette raison, il existe des méthodes définies de la manière suivante : public static Object get(Object array, int index) throws IllegalArgumentException, ArrayIndexOutOfBoundsException Toutes les exceptions héritent de la classe Exception. Pour traiter une exception, il faut utiliser un bloc try-catch. Par exemple, si on appelle la méthode get précédente, il faudrait écrire un code de ce genre: try { get(array,10); } catch (ArrayIndexOutOfBoundsException e) { /*traiter exception...*/ ... } catch (IllegalArgumentException e){ ... } Si on ne veut pas traiter les diféfrentes execptions une à une, on peut écrire try { ... } catch (Exception e) { } 8