Cours
Cours
Cours
4 techniques
●
Le pattern Observeur-Observable
✗
Les exceptions
L'architecture MVC
✗
Les types génériques en Java
Les java beans
✗
Le clonage
La sérialisation
La programmation en environnement multi-thread :
●
thread, Thread et Runnable
Cycle de vie et interruption
Les variables partagées : synchonized, interblocage, les synchronisateurs, ...
Les collections en programmation multi-thread
Le pattern producteur-consommateur
L'exécution de taches : Callable, Future, Executor
Le multi-theading en programmation graphique
ErreurMemoire1.java
Les Exceptions (1/8)
Error et Exception
public class ErreurMemoire1 {
public static void main(String[] args) {
int beaucoup = Integer.parseInt(args[0]);
Object[] tableau=new Object[beaucoup];
}
} $ java ErreurMemoire1 2000000000
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at ErreurMemoire1.main(ErreurMemoire1.java:4)
$ java ErreurMemoire1 abc
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
at
java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:481)
at java.lang.Integer.parseInt(Integer.java:514)
at ErreurMemoire1.main(ErreurMemoire1.java:3)
Throwable
Les erreurs sont des situations exceptionnelles
qui mettent en péril l'exécution d'un
programme : risque d’arrêt brutal, risque pour Exception Error
les ressources, en particulier les données.
Java en distingue 3 types :
Une Error ou une Exception est un Throwable : un "lançable" contenant un message/information sur le
problème survenu. La méthode getMessage() fournit cette information.
L'exécution ne suit plus le cours normal des instructions de contrôle : if, for, ….
Le message lancé "va directement à la fin de la méthode actuelle" ou est capturé par un try-catch. Non
capturé, il agit de même avec les méthodes appelantes jusqu'au .. main : l'exception est dite "propagée".
L'exception ArithmeticException est ici traitée/capturée via le blocs try/catch "essayer/attraper". Dès
l'exception lancée, elle va exécuter le bloc catch lui correspondant.
Equation3.java
Les Exceptions (4/8)
Propagation / Capture
...
Integer x = resoudreEquation(a,b);
if (x == null)
System.out.println("résultat équation entière ax+b=0 : pas de solution");
else
System.out.println("résultat équation entière ax+b=0 : X = "+x);
}
private static Integer resoudreEquation(int a, int b) {
try {
int sol = calculSolution(a,b);
return new Integer(sol); capture
} catch (ArithmeticException ae) {
return null;
} propagation
}
private static int calculSolution (int a, int b) {
return b/a;
}
} $ java Equation3 0 4
résultat équation entière ax+b=0 : pas de solution
La difficulté du programmeur :
Quand propage t'on une exception ?
A quel endroit capture t'on une exception ?
Que faire ? …. logger, afficher l'erreur, tenter une reprise, sauver ce qui est sauvegardable, ...
Equation4.java
Les Exceptions (5/8) Equation4Bad.java
...
private static Integer resoudreEquation(int a, int b) {
try {
int sol = calculSolution(a,b);
return new Integer(sol);
Lever une Exception
} catch (Exception e) { Changement de stratégie :
return null; le programmer détecte une situation erronée
} Il génère/lève/lance une exception
}
private static int calculSolution (int a, int b) throws Exception {
// Exception trop large ! mais c'est pour l'exemple
if (a == 0)
throw new Exception("division par 0");
else
return b/a;
} Traiter les Exceptions "obligatoires"
} Exception n'est pas une RunTimeException donc le compilateur
oblige à traiter l'erreur :
soit la capture
soit la propagation assumée : méthode qui throws l'exception
public class Equation5 {
Plusieurs catch
public static void main(String args[]) {
int a = 0, b = 0;
try { Traitement par le premier catch
a = Integer.parseInt(args[0]); correspondant au type d'exception.
b = Integer.parseInt(args[1]);
Integer x = resoudreEquation(a,b);
System.out.println("résultat équation entière ax+b=0 : X = "+x);
} catch (NumberFormatException nfe) {
System.err.println("... les paramètres a et b doivent être entier"););
} catch (Exception e) {
System.out.println("résultat ...erreur ou pas de solution ");
}
}
private static Integer resoudreEquation(int a, int b) throws Exception {
int sol = calculSolution(a,b);
return new Integer(sol);
}
private static int calculSolution (int a, int b) throws Exception {
// Exception trop large ! mais c'est pour l'exemple
if (a == 0)
throw new Exception("division par 0");
else
return b/a;
}
}
Les Exceptions (7/8) ExceptionFichier.java
import java.io.*;
public class ExceptionFichier {
finally
public static void main(String args[]) {
PrintWriter out = null;
try {
out = new PrintWriter(new FileWriter(args[0]));
for (int i = 0; i < 100; i++)
out.println("coucou : " + i);
} catch (IOException ioe) {
System.err.println("problème écriture fichier : " + ioe.getMessage())
} catch (Exception e) {
System.err.println("erreur exécution: "+ e.getMessage());
} finally {
if (out != null)
out.close();
else $ java ExceptionFichier
System.out.println("fichier non ouvert"); erreur exécution: 0
} fichier non ouvert
} $ java ExceptionFichier coucou
}
La clause finally est exécutée dans tous les cas :
après un catch d'erreur
ou après le bloc try sans erreur.
La méthode printStackTrace() de Throwable fournit l'ensemble des informations des méthodes appelées
au moment de l'erreur.
La méthode getCause() permet d'obtenir l'erreur qui a provoquer la présente erreur dans le cas d'erreur en
cascade, sinon null.
Le package java.util.logging fournit les outils pour logger les erreurs dans des fichiers.
Il est possible de créer ses propres exceptions en héritant d'Exception ou d'une de ses sous-classes, mais
il y en a déjà certainement une qui vous convient.
Vers la généricité (0/13) Autoboxing0.java
Autoboxing Unboxing
Ce n'est pas de la généricité mais cela aide le programmeur dans le même sens !
public class Autoboxing0 { $ java Autoboxing0
... le3eme = 2
List liste = new ArrayList(); le13eme = 13
for (int i = 0; i < 10; i++)
liste.add(new Integer(i));
int le3eme = ((Integer)liste.get(2)).intValue();
System.out.println("le3eme = "+le3eme);
// et maintenant :
for (int j = 11; j < 20; j++)
liste.add(j);
int le13eme = (Integer)liste.get(12);
System.out.println("le13eme = "+le13eme);
Il est possible de créer des classes de structures contenant n'importe quels type d'objet grâce :
aux collections (classes implémentant l'interface Collection)
et à la superclasse Object
Problèmes
- manipuler les objets référencés par ces structures oblige à faire du transtypage vers le bas (downcasting)
- risque d'erreur de cast, repérables uniquement à l'exécution (ClassCastException)
- impossibilité d'obliger à ce qu'une collection ne contienne qu'un seul type d'objet (comme avec les
tableaux)
Generic1.java
Types Génériques (2/13) Generic2.java
Remarque hors généricité : le paramètre … est un vararg : ça permet de passer un nombre non défini
d'arguments d'un même type à une méthode. Le paramètre correspondant est un tableau de ce type.
Paire.java
Types Génériques (4/13)
public class Paire <T> {
Déclarer une classe "générique"
private T premier, second;
public Paire(T premier, T second) {
this.premier = premier; this.second = second;
}
public String toString() { return "("+premier.toString()+","
+second.toString()+")"; }
public T getPremier() { return this.premier; }
public T getSecond() { return this.second; }
public void setPremier(T premier) { this.premier = premier; }
public void setSecond(T second) {this.second = second; }
}
La notation <T> indique que la classe Paire utilise une variable de type :
T est le paramètre formel de type. Il peut y en avoir plusieurs.
La compilation de la classe "générique" donne simplement un fichier Paire.class
public class UsePaire1 {
public static void main(String[] args) {
Paire<Double> point = new Paire<Double>(1.2, 4.5);
point.setSecond(point.getPremier() + 3.4);
System.out.println(point.toString());
...
public class UsePaire3 {
public static void main(String[] args) {
List<Paire<Double>> listePoints = new ArrayList<>();
listePoints.add(new Paire<Double>(1.2, 4.5)); Il est possible d'utiliser des
listePoints.add(new Paire<>(1.2, 4.5)); génériques comme type passé.
System.out.println(listePoints.toString());
Paire<Paire<Double>> pairePoints = new Paire<Paire<Double>>(
new Paire<Double>(1.2, 4.5), new Paire<>(1.2, 4.5) );
System.out.println(pairePoints.toString());
...
$ java7 UsePaire3
[(1.2,4.5), (1.2,4.5)]
((1.2,4.5),(1.2,4.5))
Paire.java
Types Génériques (7/13) UsePaire1.java
UsePaire4.java
public class Paire <T> {
L'Erasure
private T Object premier, second;
public Paire(T Object premier, T Object second) {
this.premier = premier; this.second = second; barré = effacer
} italique = ajouter
public T Object getPremier() { return this.premier; }
public void setPremier(T Object premier) {
...
}
public class UsePaire1 {
public static void main(String[] args) {
Paire<Double> point = new Paire<Double>(1.2, 4.5);
point.setSecond((Double)point.getPremier() + 3.4);
...
... La compilation vérifie l'adéquation des types puis "efface" la
partie générique pour ne laisser que le type raw et des casts.
Avantages :
- 1 seule classe compilée : le raw type
- pas de temps supplémentaire à l'exécution
Inconvénient :
- perte d''information sur le type passé
public class UsePaire4 {
public static void main(String[] args) {
Paire<Double> [] tabPoints = new Paire<Double>[2];
// compilation : error: generic array creation
L'érasure efface le type, or un tableau doit contenir des informations sur le type de ses éléments.
Il n'est pas possible de créer des tableaux d'éléments génériques mais des collections oui.
Types Génériques (8/13) MauvaisePaire.java
L'Erasure
public class MauvaisePaire <T> {
private T premier, second;
public MauvaisePaire(T premier, T second) {
this.premier = premier; this.second = second;
}
public MauvaisePaire() {
3 erreurs à la compilation
this.premier = this.second = new T();
conséquence de l'érasure :
} - impossible d'instancier un objet
public boolean equals(Object obj) { sans son type
if (obj instanceof T) { - impossible de vérifier un type qui
T autre = (T)obj; n'existe pas
return this.premier.equals(autre.premier) && - impossible d'instancier tableau
this.second.equals(autre.second); sans le type de ses éléments
} else
return false;
}
public T[] asArray() {
T[] tab = new T[2];
tab[0] = this.premier; tab[1] = this.second;
return tab;
}
} Donc dans la classe générique :
ne pas instancier d'objet du type paramètre
ne pas utiliser l'opérateur instanceof avec le paramètre type
ne pas instancier de tableau dont les éléments sont du type paramètre
PaireOrdonnee.java
Types Génériques (9/13) UsePaireOrdonnee.java
La notation <TypeParameter extends Class & Interface 1 & ... & Interface N >
introduit des contraintes sur le type paramètre :
- hériter d'1 classe
- implémenter des interfaces.
Cela réduit les types possibles
et permet d'utiliser les méthodes non static de la classe héritée et/ou des interfaces implémentées.
Les contraintes peuvent être de toutes classes, interfaces, et énumérations, voire de type paramètre
(PaireOrdonnee <T extends Comparable<T>>)
sauf les types primitifs et les arrays.
Outils1.java
Types Génériques (10/13) UseOutils1.java
Méthodes génériques
import java.util.*;
public class Outils1 {
public static <T extends Comparable<T>> T max(T val1, T val2) {
if (val1.compareTo(val2) > 0)
return val1;
else
return val2;
}
}
Les méthodes static et non static comme les constructeurs peuvent avoir des types paramètres.
Le ou les type paramètres doivent apparaître entre < > avant le type retour.
L'appel de telle méthode se fait comme les autres méthodes en utilisant l'inférence de type.
public class UseOutils1 {
public static void main(String[] args) {
Integer i = new Integer(1);
Integer j = new Integer(2); $ java7 UseOutils1
System.out.println(Outils1.max(i,j)); 2
...
Collections.java
Types Génériques (11/13)
La notation avec wilcard ? : <? super type > <? extends type > voire <?>
introduit une contrainte supérieure ou inférieure sur le type paramètre.
Comparaison avec les type paramètres contraints :
- n'introduit qu'une contrainte
- mais la contrainte peut être inférieure : <? super type >
- et la contrainte peut être relative à un type array.
HeritageGeneric1.java
Types Génériques (12/13) HeritageGeneric2.java
Héritage et généricité
public class HeritageGeneric1 {
public static void main(String[] args) {
Paire<Double> point = new Paire<Double>(1.2, 4.5);
Paire<Number> point2 = point;
} $ javac HeritageGeneric1.java
} HeritageGeneric1.java:4: incompatible
types
found : Paire<java.lang.Double>
required: Paire<java.lang.Number>
Paire<Number> point2 = point;
1 error ^
Piège :
Bien que Double hérite de Number, Paire<Double> n'hérite pas de Paire<Number>.
Mais ArrayList implémente List qui implémente Collection, donc
ArrayList<String> hérite de List<String> qui hérite Collection<String>.
Remarque : ArrayList<String> « hérite » de son raw type ArrayList
public class HeritageGeneric2 {
public static void main(String[] args) {
ArrayList<String> phrase = new ArrayList<String>();
phrase.add("il"); phrase.add("fait");
phrase.add("beau");
$ java HeritageGeneric2
List<String> phrase2 = phrase;
[il, fait, beau]
Collection<String> phrase3 = phrase;
System.out.println(phrase3.toString());
Generic4.java
Fin de la généricité (13/13)
La boucle foreach
Ce n'est pas de la généricité mais cela aide le programmeur dans le même sens !
public class Generic4 {
public static void main(String[] args) {
ArrayList<String> phrase = new ArrayList<String>();
phrase.add("il"); phrase.add("fait"); phrase.add("beau");
String tout = "";
for (Iterator<String> iter = phrase.iterator(); iter.hasNext(); )
tout = tout + " " + iter.next();
System.out.println("tout : "+tout);
String tout2 = "";
for (String mot : phrase) $ java Generic4
tout2 = tout2 + " " + mot; tout : il fait beau
System.out.println("tout2 : "+tout2); tout2 : il fait beau
»
e
ou air
pi
t» e
co
«
Objet copié
●
Attribut valeur 1 0111010111010010001011
●
Attribut valeur 2
Le clonage permet d'obtenir une copie d'un La sérialisation permet d'obtenir une copie sous
objet : forme binaire (voire en XML) d'un objet.
Utile pour conserver un état, pour la Utile pour conserver un objet dans un fichier ou le
programmation parallèle, voire pour créer un transmettre via le réseau. D’où la persistance et RMI.
objet complexe (car l'appel des De plus, l'objet peut être instancié à partir de cette
constructeurs est coûteux en temps). copie sérialisée.
Ces 2 opérations pré-existent pour tout objet dans la JVM : les types primitifs doivent être clonés ou
sérialisés de manière unique. Mais ces 2 opérations ne sont autorisées que si le programmeur le fait. Les
2 interfaces Serialization et Cloneable sont dites interface de marquage : elles servent à indiquer que
l'opération est autorisée.
Les difficultés surviennent quand un objet est lui-même composé d'autres objets : comment appliquer
"récursivement" le clonage ou la sérialisation ?
Puce1.java
Sérialisation (1/13) Persistance0.java
ObjectOutputStream
public class Puce1 {
String nom = null; $ java Persistance0
int nombrePattes = 1; puce : Puce nom : pupuce, nombre de pattes = 6
public Puce1(String n, int nb) { java.io.NotSerializableException: Puce1
nom = n; nombrePattes = nb; at java.io.ObjectOutputStream.
} writeObject0(ObjectOutputStream.java:1180)
public String toString() { at java.io.ObjectOutputStream.
writeObject(ObjectOutputStream.java:346)
... at Persistance0.main(Persistance0.java:9)
public class Persistance0 {
public static void main(String[] args) {
try {
Puce1 puce = new Puce1("pupuce", 6);
System.out.println("puce : "+puce.toString());
FileOutputStream fos = new FileOutputStream("sauvePuce");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(puce);
oos.close();
} catch (Exception e) {
e.printStackTrace();
...
La sérialisation, par défaut, encode chaque variable de type primitif et récursivement sérialise les
références à des objets contenus. L'ensemble est mis bout à bout : en série.
Remarque : String implémente l'interface Serializable.
...
La classe ObjectOutputStream a une méthode writeObject() pour sérialiser un objet sérialisable dans un flot.
Et la classe ObjectInputStream a une méthode readObject() pour de-sérialiser : il faut cast-er l'objet dans la
bonne classe.
$ java Persistance5
puce : Puce nom : pupuce, nombre de pattes = 6
puce : null
puce : Puce nom : pupuce, nombre de pattes = 6
Vect.java
Sérialisation (4/13)
variable transient
import java.io.* ;
public class Vect implements Serializable {
private Object[] tabElement = new Object[10];
private transient int taille = 0;
public Object elementAt(int index)
throws ArrayIndexOutOfBoundsException {
if (index >= taille)
throw new ArrayIndexOutOfBoundsException(index);
else
return tabElement[index];
}
public void add(Object element) { Une réécriture rapide et
if (tabElement.length == taille) incomplète de Vector
….
resize(tabElement.length*2);
tabElement[taille++] = element;
}
public String toString() {
StringBuffer res = new StringBuffer("[ ");
for (int i = 0 ; i < taille1; ++i)
res.append(tabElement[i]+", ");
if (taille > 0)
res.append(tabElement[taille1]+" ");
res.append("]");
return res.toString();
}
Vect.java
Sérialisation (5/13)
… suite
private void resize(int nouvelleTaille) {
Object[] newTabElement = new Object[nouvelleTaille];
System.arraycopy(tabElement, 0, newTabElement, 0, nouvelleTaille);
tabElement = newTabElement;
taille = nouvelleTaille;
}
private void writeObject(ObjectOutputStream flotOut) Du fait de la réduction
avant sérialisation, la
throws IOException {
taille correspond à celle
if (tabElement.length > taille)
du tableau.
resize(taille); Les variables transient
flotOut.defaultWriteObject(); ne sont pas sérialisées.
}
private void readObject(ObjectInputStream flotIn)
throws IOException, ClassNotFoundException
{
flotIn.defaultReadObject();
taille = tabElement.length;
}
}
Pour redéfinir la sérialisation d'un objet, il faut implémenter des méthodes private writeObject() et
readObject() bien qu'elles ne fassent parties d'aucune interface !
Les méthodes defaultWriteObject() et defaultReadObject() sont les méthodes de sérialisation par défaut.
Sérialisation (6/13) TestVect1.java
public class TestVect1 {
public static void main(String args[]) {
try {
Vect vect = new Vect(); $ java TestVect1
vect2 : [ Puce nom : pupuce, nombre de pattes = 6,
vect.add(new Puce5("pupuce", 6));
Puce nom : zezette, nombre de pattes = 5 ]
vect.add(new Puce5("zezette", 5));
ObjectOutputStream flotEcriture =
new ObjectOutputStream(new FileOutputStream("Vect_Puce5"));
flotEcriture.writeObject(vect);
flotEcriture.close();
ObjectInputStream flotLecture =
new ObjectInputStream(new FileInputStream("Vect_Puce5")); La classe Vect est
Vect vect2 = (Vect)flotLecture.readObject(); sérialisable !
flotLecture.close();
System.out.println("vect2 : "+vect2.toString());
} catch (IOException ioe) {
System.out.println(" erreur :" + ioe.toString());
} catch (ClassNotFoundException cnfe) {
System.out.println(" erreur :" + cnfe.toString());
}
}
} $ java TestVect2
import java.io.*; erreur :java.io.NotSerializableException: Puce1
public class TestVect2 {
public static void main(String args[]) {
try {
Erreur normale : le Vect vect = new Vect();
mécanisme récursif vect.add(new Puce5("pupuce", 6));
de sérialisation tombe
vect.add(new Puce1("zezette", 5));
sur un objet non ObjectOutputStream flotEcriture =
sérialisable. new ObjectOutputStream(new FileOutputStream("Vect_Puce1et5"));
flotEcriture.writeObject(vect);
...
TestVect1.java
Sérialisation (7/13)
public class TestVect1 {
public static void main(String args[]) {
try {
Vect vect = new Vect();
vect.add(new Puce5("pupuce", 6));
vect.add(new Puce5("zezette", 5));
ObjectOutputStream flotEcriture =
new ObjectOutputStream(new FileOutputStream("Vect_Puce5"));
flotEcriture.writeObject(vect);
flotEcriture.close();
ObjectInputStream flotLecture =
new ObjectInputStream(new FileInputStream("Vect_Puce5"));
Vect vect2 = (Vect)flotLecture.readObject();
flotLecture.close();
System.out.println("vect2 : "+vect2.toString());
} catch (IOException ioe) {
System.out.println(" erreur :" + ioe.toString());
} catch (ClassNotFoundException cnfe) {
System.out.println(" erreur :" + cnfe.toString());
}
}
}
La classe Vect est sérialisable ! $ java TestVect1
Et récursivement Puce5 vect2 : [ Puce nom : pupuce, nombre de pattes = 6,
Et récursivement String Puce nom : zezette, nombre de pattes = 5 ]
TestVect2.java
Sérialisation (8/13)
import java.io.*;
public class TestVect2 {
public static void main(String args[]) {
try {
Vect vect = new Vect();
vect.add(new Puce5("pupuce", 6)); $ java TestVect2
vect.add(new Puce1("zezette", 5)); erreur :java.io.NotSerializableException: Puce1
ObjectOutputStream flotEcriture =
new ObjectOutputStream(new FileOutputStream("Vect_Puce1et5"));
flotEcriture.writeObject(vect);
...
méthode clone()
public class Puce1 {
String nom = null; $ java Clonage1
lesPuces : [Puce nom : zezette, nombre de pattes = 6]
int nombrePattes = 1; encoreLesPuces : [Puce nom : zezette, nombre de pattes = 6,
... Puce nom : teigneuse, nombre de pattes = 5]
public class Clonage1 {
public static void main(String[] args) {
Puce1 puceA = new Puce1("pupuce", 6);
Puce1 puceB = new Puce1("teigneuse", 5);
ArrayList<Puce1> lesPuces = new ArrayList<Puce1>();
lesPuces.add(puceA); lesPuces.add(puceB);
ArrayList<Puce1> encoreLesPuces = (ArrayList<Puce1>) lesPuces.clone();
lesPuces.remove(puceB);
lesPuces.get(0).nom = "zezette";
System.out.println("lesPuces : "+lesPuces.toString());
System.out.println("encoreLesPuces : "+encoreLesPuces.toString());
...
La classe ArrayList possède une méthode clone().
Une fois cloné, enlever un objet d'un des vecteurs ne l'enlève pas dans l'autre.
Mais changer une propriété d'un objet d'un des vecteurs opère de même dans l'autre.
Donc juste après clone() :
lesPuces EncoreLesPuces
Pupuce,6
Teigneuse,5
Puce2.java
Clonage (10/13)
interface Cloneable
public class Puce2 implements Cloneable {
StringBuffer nom = null;
La classe Object possède une
int nombrePattes = 1;
public Puce2(String n, int nb) { méthode :
nom = new StringBuffer(n); nombrePattes = nb; protected Object clone() throws
} CloneNotSupportedException
public String toString() {
return "Puce nom : " + nom.toString() Par défaut, elle copie les valeurs
+", nombre de pattes = " + nombrePattes; des variables donc les types
} primitifs et les références sur les
public Object clone() { Objets. Pour l'utiliser, il fautla
try { rendre public.
return super.clone();
} Si l'objet n'implémente pas
catch (CloneNotSupportedException e) {
Cloneable, elle lance l'exception
throw new InternalError(e.getMessage());
}
CloneNotSupportedException.
}
}
La classe Puce2 pour implémenter l'interface Cloneable doit redéfinir public la méthode clone().
Elle adopte ici le comportement par défaut.
Puce2.java
Clonage (11/13) Clonage2.java
public class Clonage2 {
public static void main(String[] args) {
Puce2 puce = new Puce2("pupuce", 6);
Puce2 puceCopie = (Puce2) puce.clone();
System.out.println("puce : "+puce.toString());
System.out.println("puceCopie : "+puceCopie.toString());
puce.nom.append(" savante");
puce.nombrePattes = 5;
System.out.println("puce : "+puce.toString());
System.out.println("puceCopie : "+puceCopie.toString());
}
}
$ java Clonage2
puce : Puce nom : pupuce, nombre de pattes = 6
puceCopie : Puce nom : pupuce, nombre de pattes = 6
puce : Puce nom : pupuce savante, nombre de pattes = 5
puceCopie : Puce nom : pupuce savante, nombre de pattes = 6
public class Puce3 implements Cloneable {
...
public Object clone() {
try {
Puce3 copie = (Puce3) super.clone();
copie.nom = new StringBuffer(this.nom.toString());
return copie;
}
catch (CloneNotSupportedException e) {
...
$ java Clonage3
puce : Puce nom : pupuce, nombre de pattes = 6
puceCopie : Puce nom : pupuce, nombre de pattes = 6
puce : Puce nom : pupuce savante, nombre de pattes = 5
puceCopie : Puce nom : pupuce, nombre de pattes = 6
Paralléliser
public class Moyenne ... $ java Moyenne
Scanner scanIn = new Scanner(System.in); 4,6
double total = 0.0; int nombre = 0; 8,9
while (scanIn.hasNextDouble()) { =
total += scanIn.nextDouble(); Moyenne des 2 nombres = 6.75
nombre ++;
}
System.out.println("Moyenne des "+nombre Problème : chacun passe beaucoup de temps
+" nombres = "+(total/nombre)); bloqué en attente de lecture
... Comment faire ces 2 taches "en même temps" ?
Thread
public class DeuxTachesParalleles {
public static void main (String [] args) {
Scanner scanIn = new Scanner(System.in);
double total = 0.0; int nombre = 0;
Thread autreTache = new Thread(new Lent());
autreTache.start();
while (scanIn.hasNextDouble()) {
total += scanIn.nextDouble();
nombre ++;
}
System.out.println("Moyenne des "+nombre+" nombres = "+(total/nombre));
}
}
La classe Thread comporte plusieurs méthodes d'administration des threads (fil d'exécution d'un Runnable).
En particulier, la méthode start() qui demande à la JVM un nouveau fil d'exécution pour la méthode run .
Les 2 threads (fil d'exécution) sont bien indépendants car l'un peut s’arrêter avant l'autre.
$ java DeuxTachesParalleles
chargement tres lent
chargement tres tres lent
chargement tres tres tres lent
4,chargement tres tres tres tres lent
5chargement tres tres tres tres tres lent
chargement tres tres tres tres tres tres lent
6,chargement tres tres tres tres tres tres tres lent
7chargement tres tres tres tres tres tres tres tres lent
chargement tres tres tres tres tres tres tres tres tres lent
=chargement tres tres tres tres tres tres tres tres tres tres lent
Moyenne des 2 nombres = 5.6
DeuxTachesParalleles2.java
Thread (4/38)
la classe Thread
public class DeuxTachesParalleles2 {
...
Thread autreTache = new Thread(new Lent());
autreTache.start();
System.out.println("autreTache: "+autreTache.toString());
System.out.println("Thread.currentThread(): "+Thread.currentThread().toString());
while (scanIn.hasNextDouble()) {
total += scanIn.nextDouble();
nombre ++;
... $ java DeuxTachesParalleles2
autreTache: Thread[Thread0,5,main]
Thread.currentThread(): Thread[main,5,main]
chargement tres lent
...
La méthode toString() de la classe Thread affiche le nom du thread, son niveau de priorité et le nom de
son groupe.
La méthode statique currentThread() fournit le présent thread en train d'exécuter la méthode.
Fin du Runnable
blocage Prêt à s'exécuter
En attente du
Not runnable Waiting processeur
Bloqué sur une
méthode
yield()
read() … Ordonnancement :
sleep() Il n'y a aucune garantie d'algorithme
join() de tourniquet équitable ! Il peut faire du
wait() "time-slicing".
Dépend du niveau de priorité du thread
Running
exécute les instructions
de la méthode run
import java.util.*;
public class Arret1 {
public static void main (String [] args) {
boolean encore = true;
Scanner scanIn = new Scanner(System.in);
Thread tacheDodo = new Thread(new Dodo());
Thread tacheInfinie = new Thread(new BoucleInfinie());
tacheDodo.start();
tacheInfinie.start();
while (encore) { $ java Arret1
String frappe = scanIn.next(); dodo
if (frappe.equals("q")) { dodo
encore = false; dodo
dodo
tacheDodo.interrupt();
dodo
tacheInfinie.interrupt();
dodo
} dodo
} dodo
} ....2000000000
} dodo
L'arrêt d'un thread peut se faire : dodo
normalement en fin de méthode run dodo
"brusquement" par une RuntimeException ou une Error dodo
ou l’arrêt du processus. qdodo
public class BoucleInfinie2 implements Runnable {
public void run() {
boolean encore = true;
int j = 1;
while (encore && ! Thread.currentThread().isInterrupted()) {
for (int i=0; i<2000000000; ++i)
j = j*i ;
System.out.println("....2000000000");
}
}
} La méthode isInterrupted() renseigne sur l'état interrompu ou non du thread.
public class Dodo2 implements Runnable {
public void run() {
try {
for (int i=0; i<20 && ! Thread.currentThread().isInterrupted() ; i++) {
System.out.println("dodo");
Thread.sleep(500);
}
} catch (InterruptedException ie) { /* fin */}
}
}
Dodo2.java
Thread (9/38) : Comment arrêter un thread ? Arret2.java
InterruptedException
public class Dodo2 implements Runnable {
public void run() {
try {
for (int i=0; i<20 && ! Thread.currentThread().isInterrupted() ; i++) {
System.out.println("dodo");
Thread.sleep(500);
}
} catch (InterruptedException ie) { /* fin */}
}
}
Soit le thread est sur une méthode bloquante interruptible par l'exception InterruptedException
(exemple : sleep, wait, …) ,
alors l'exception est lancée donc try-catchée et l'état "isInterrupted" passe à true,
Soit le thread est sur une méthode bloquante non interruptible par l'exception InterruptedException
(exemple : read sur socket, lock de verrou, select de Selector …),
ou le thread est sur des instructions et méthodes non bloquantes,
alors l'état "isInterrupted" passe à true.
Dans le thread de "Dodo", si interrupt() se produit pendant la méthode bloquante sleep alors
l'interruption est lancée puis catch-ée à la fin de la méthode run() donc fin.
Par contre, très rarement, si interrupt() se produit ailleurs, l'état "isInterrupted" passe à true, donc la
boucle stoppe.
VariablePartagee1.java
Thread (10/38)
Variables partagées
public class VariablePartagee1 {
private int compteur = 0;
public static void main (String [] args) throws InterruptedException {
VariablePartagee1 vp = new VariablePartagee1();
vp.go(); $ java VariablePartagee1
} Fin : compteur = 20000000
public void go() throws InterruptedException { Fin : compteur = 15664865
Thread increment1 = new Thread(new Incrementation());
Thread increment2 = new Thread(new Incrementation());
increment1.start();
increment1.join();
La méthode bloquante join() attends
increment2.start(); la fin d'exécution du thread indiqué.
increment2.join(); Elle peut être interrompue par
System.out.println("Fin : compteur = "+compteur); l'InterruptedException.
compteur = 0;
increment1 = new Thread(new Incrementation()); Dans le 1er cas (exécution
increment2 = new Thread(new Incrementation()); séquentielle des 2 threads), la
increment1.start(); variable partagée compteur est
increment2.start(); finalement incrémentée de la bonne
increment1.join(); somme.
increment2.join();
Ce n'est pas le cas dans la 2nde
System.out.println("Fin : compteur = "+compteur);
}
(exécution parallèle des 2 threads)
class Incrementation implements Runnable { car l'instruction compteur++ est
public void run() { composée d'au moins 3 opérations
for (int i=0; i<10000000; ++i) dans le bytecode Java. Si
compteur++; l'ordonnanceur change de thread au
} milieu de ces opérations …..
}
}
VariablePartagee2.java
Thread (11/38) : Variables partagées
synchronized
public class VariablePartagee2 {
class Compteur {
public int val = 0; $ java VariablePartagee2
} Fin : compteur = 20000000
... Fin : compteur = 20000000
public void go() throws InterruptedException {
Thread increment1 = new Thread(new Incrementation());
Thread increment2 = new Thread(new Incrementation());
increment1.start(); L'utilisation de l'instruction de
increment1.join();
increment2.start();
contrôle synchronized résout le
increment2.join(); problème mais l'exécution prend plus
System.out.println("Fin : compteur = "+compteur.val); de temps.
compteur = new Compteur();
increment1 = new Thread(new Incrementation()); Chaque objet possède un verrou :
increment2 = new Thread(new Incrementation()); Pour "entrer" dans le bloc
increment1.start(); { compteur.val++; } le thread doit
increment2.start(); acquérir le verrou de compteur;
increment1.join(); attente bloquante ;
increment2.join(); tant que qu'il exécute le bloc, il
System.out.println("Fin : compteur = "+compteur.val);
possède et est le seul;
}
class Incrementation implements Runnable {
A la fin du bloc, il déverrouille;
public void run() { Un autre thread en attente peut alors
for (int i=0; i<10000000; ++i) acquérir le verrou.
synchronized(compteur) {
compteur.val++; L'exclusion d'accès à l'incrémentation
} de la variable partagée est assurée.
}
}
VariablePartagee3.java
Thread (12/38) : Variables partagées
méthode synchronized
public class VariablePartagee3 { $ java VariablePartagee3
class Compteur { Fin : compteur = 20000000
public int val = 0; Fin : compteur = 20000000
public synchronized void plusplus() {
compteur.val++;
}
} Une méthode synchronized est
... équivalente à synchronized(this).
class Incrementation implements Runnable {
public void run() {
for (int i=0; i<10000000; ++i)
compteur.plusplus();
}
}
} Le mécanisme de verrou interne est ré-entrant :
un thread ayant un verrou peut entrer dans un autre bloc synchronized sur le même verrou
synchronized(obj) {
...
synchronized(obj) {
…
}
}
"Thread1" prio=10 tid=0x8c129400 nid=0x163b waiting for monitor entry [0x8ba15000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Interblocage$Redistribution.run(Interblocage.java:35)
waiting to lock <0xa9b84900> (a Interblocage$CompteBancaire)
locked <0xa9b84918> (a Interblocage$CompteBancaire)
at java.lang.Thread.run(Thread.java:679)
"Thread0" prio=10 tid=0x8c127800 nid=0x163a waiting for monitor entry [0x8ba66000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Interblocage$Redistribution.run(Interblocage.java:35)
waiting to lock <0xa9b84918> (a Interblocage$CompteBancaire)
locked <0xa9b84900> (a Interblocage$CompteBancaire)
at java.lang.Thread.run(Thread.java:679)
Problème : Problème :
L'accès aux variables de type long et L'optimisation du code conduit à stocker des valeurs de
double n'est pas atomique : il nécessite 2 variables en cache avant affectation en mémoire. Donc
instructions de byte-code. la valeur en mémoire n'est pas forcément la bonne !
Solution : Solution :
le package java.util.concurrent.atomic qui Déclarer la variable volatile pour empêcher la mise en
fournit une classe AtomicLong cache de sa valeur.
Problème : Problème :
Le mécanisme de verrou interne est basé sur le L'optimisation du code peut changer
volontariat des programmeurs. Un autre thread pourrait l'ordre d'exécution des instructions qui
accéder faire compteur.val sans, au préalable, acquérir le semblent indépendantes.
verrou compteur. Solution :
Solution : Connaître les règles du MMJ modèle de
Utiliser le pattern moniteur de Java mémoire de la JVM !
public class ClasseMonitorée {
private final Object monVerrou = new Object();
@GuardedBy("monVerrou") type objetGardé;
void méthode() {
synchronized(monVerrou) {
// accès à objetGardé
}
}
}
L'annotation GuardedBy est pratique pour hériter !
NombresAtomiques.java
Thread (16/38) : partage thread-safe de CalculComplique3.java
variable : AtomicNumber
import java.util.concurrent.atomic.AtomicInteger;
public class CalculComplique3 implements Runnable {
private int x;
private AtomicInteger result;
public CalculComplique3(int x, AtomicInteger r) { Les variables "atomiques" du
this.x=x; this.result=r; package java.util.concurrent.atomic
}
répondent aux manques d'opérations
public void run() {
int y = x;
atomiques sur les variables volatiles.
for (int i=0; i< 100000; i++)
for (int j=0; j< 100000; j++) Ses méthodes addAndGet(),
y += 5; incrementAndGet(),
result.addAndGet(y); compareAndSet() sont toutes
} effectuées en bloc synchonized(this).
}
La classe est évidemment
public class NombresAtomiques { "thread-safe".
...
Scanner scanIn = new Scanner(System.in);
int nombre1 = scanIn.nextInt();
int nombre2 = scanIn.nextInt();
AtomicInteger resultat = new AtomicInteger(0);
Thread tache1 = new Thread(new CalculComplique3(nombre1, resultat));
Thread tache2 = new Thread(new CalculComplique3(nombre2, resultat));
tache1.start(); tache2.start();
tache1.join(); tache2.join();
System.out.println("Somme de CalculComplique3 de "+nombre1
+" et "+nombre2+" = "+resultat.intValue());
. . .
TachesAvecBarriere.java
Thread (17/38): partage thread-safe de CalculComplique4.java
variable : Synchronisateur ….
public class CalculComplique4 implements Runnable {
private int x; CountDownLatch
private AtomicInteger result;
private CountDownLatch barriere;
public CalculComplique4(int x, AtomicInteger r, CountDownLatch b) {
this.x=x; this.result=r; barriere=b;
}
public void run() {
int y = x; Les objets (donc classe) synchronisateurs
for (int i=0; i< 100000; i++) régulent le flux des threads.
for (int j=0; j< 100000; j++) CountDownLatch est un synchonisateur de type
y += 5; loquet à décrémentation :
result.addAndGet(y);
le loquet est "armé" avec une certaine valeur
barriere.countDown();
}
entière,
} il est décrémenté par la méthode countDown() ,
la méthode await() met en attente le thread
jusqu'à ce que le loquet soit à zéro.
public class TachesAvecBarriere { $ java TachesAvecBarriere
... 5
int nombre = scanIn.nextInt(); Somme de CalculComplique4
AtomicInteger resultat = new AtomicInteger(0); de 1 à 5 = 891896857
CountDownLatch barriere = new CountDownLatch(nombre);
for (int i=1; i<=nombre; i++) {
Thread tache = new Thread(new CalculComplique4(nombre, resultat, barriere));
tache.start();
}
barriere.await();
System.out.println("Somme CalculComplique4 de 1 à "+nombre+" = "+resultat.intValue());
Thread (18/38)
les synchronisateurs
De base :
volatile "synchronise" la valeur de la variable,
synchronized(objet) est un verrou à un seul accès
wait() et notify() et notifyAll() sont, en complément de synchronized, un
dispositif blocage/déblocage.
Vector
public class TestVector3 {
private Vector<String> monVector = new Vector<String>(); $ java TestVector3
public static String getLast(Vector<String> vect) { monVector.getLast() :
synchronized(vect) { 3999ième
int lastIndex = vect.size()1; ...
return vect.get(lastIndex); monVector.getLast() :
} 2000ième
} $
public static void deleteLast(Vector<String> vect) {
synchronized(vect) {
int lastIndex = vect.size()1;
vect.remove(lastIndex);
}
}
. . .
Le verrouillage par synchronized offre une solution.
Attention à ne pas en abuser sur des temps longs, sinon ça ne sert à rien de paralléliser.
Mauvais exemple : synchronized(vecteur) {
for (int i=0; i<vecteur.size(); ++i)
méthodeAFaire(vecteur.get(i));
}
Iterator
public class TestVector4 {
private Vector<String> monVector = new Vector<String>();
public static void main (String [] args) throws InterruptedException {
TestVector4 test = new TestVector4();
test.go(); La documentation de Vector
} précise que l'iterator fourni
public void go() throws InterruptedException { est "fail-fast" : une
Thread tache = new Thread(new RemoveurVector(monVector)); ConcurrentModificationException
for (int i=0; i<4000; ++i) est levée quand un thread
monVector.add(i+"ième "); remove un élément de la
tache.start();
collection.
Iterator parcoursMonVector = monVector.iterator();
while (parcoursMonVector.hasNext())
System.out.println("parcoursMonVector.next() = "+parcoursMonVector.next());
tache.join();
} Pour être "thread-safe", try-catcher l'exception
class RemoveurVector implements Runnable {
bien que ce ne soit pas obligatoire ou changer
private Vector<String> leVector;
public RemoveurVector(Vector<String> l) { de stratégie de programmation.
leVector = l;
}
public void run() { $ java TestVector4
for (int i=0; i<100; ++i) { ...
leVector.remove((int)(Math.random() parcoursMonVector.next() = 109ième
*leVector.size())); Exception in thread "main"
} java.util.ConcurrentModificationException
} At ...
} at TestVector4.go(TestVector4.java:15)
} at TestVector4.main(TestVector4.java:6)
Thread (23/38) : Collections et Multithreading :
CopyOnWriteArrayList TestCopyOnWriteArrayList.java
du package java.util.concurrent
import java.util.concurrent.*;
public class TestCopyOnWriteArrayList {
private CopyOnWriteArrayList<String> maListe = new CopyOnWriteArrayList<String>();
. . .
public void go() throws InterruptedException {
Thread tache = new Thread(new RemoveurCopyOnWriteArrayList(maListe));
for (int i=0; i<4000; ++i)
maListe.add(i+"ième ");
Iterator parcoursListe = maListe.iterator();
tache.start();
while (parcoursListe.hasNext())
System.out.println("parcoursListe.next() = "+parcoursListe.next());
tache.join();
}
class RemoveurCopyOnWriteArrayList implements Runnable {
private CopyOnWriteArrayList<String> laListe;
public RemoveurCopyOnWriteArrayList(CopyOnWriteArrayList<String> l) {
laListe = l;
}
public void run() {
for (int i=0; i<100; ++i) {
laListe.remove((int)(Math.random()*laListe.size()));
}
}
} Le package java.util.concurrent fournit des classes pour la programmation multithread.
} La classe CopyOnWriteArrayList est "totalement" "thread-safe" :
ses méthodes sont fort coûteuse en temps d'exécution.
Sa méthode iterator() réalise une copie de la liste comme précédemment suggéré.
ProductionConsommation.java
Thread (24/38) CalculComplique5.java
L'ArrayBlockingQueue synchronisateur
et le Pattern Producteur-Consommateur
Thread
e
up è re un calculateur
Thread main : c
En ré
Dépose au fer et à mesure les
valeurs fournies par Thread
l'utilisateur File des valeurs à calculer
calculateur
Thread
calculateur
Le(s) producteurs produisent,
Déposent leur produit dans la file d'attente,
Le(s) consommateurs consomment les produits,
Attendent si elle est pleine
Les prennent dans la file d'attente,
Attendent si elle est vide
public class ProductionConsommation {
public static void main (String [] args) throws InterruptedException {
ArrayBlockingQueue<Integer> fileDeJob = new ArrayBlockingQueue<Integer>(5);
for (int i=0; i<=3; i++) {
Thread tache = new Thread(new CalculComplique5(fileDeJob));
tache.start();
}
Scanner scanIn = new Scanner(System.in); $ java ProductionConsommation
while (scanIn.hasNextInt()) 4 5 6 7 8
CalculComplique5(4) = 784662532
fileDeJob.put(scanIn.nextInt());
CalculComplique5(6) = 784662534
CalculComplique5(5) = 784662533
L'ArrayBlockingQueue est une collection « file » thread-safe. CalculComplique5(7) = 784662535
La méthode put() y dépose un élément ou attend si la file est 9
CalculComplique5(8) = 784662536
pleine. CalculComplique5(9) = 784662537
^C
ProductionConsommation.java
Thread (25/38) CalculComplique5.java
L'ArrayBlockingQueue synchronisateur
et le Pattern Producteur-Consommateur
public class ProductionConsommation {
public static void main (String [] args) throws InterruptedException {
ArrayBlockingQueue<Integer> fileDeJob = new ArrayBlockingQueue<Integer>(5);
for (int i=0; i<=3; i++) {
Thread tache = new Thread(new CalculComplique5(fileDeJob));
tache.start();
}
Scanner scanIn = new Scanner(System.in);
while (scanIn.hasNextInt())
fileDeJob.put(scanIn.nextInt());
L'ArrayBlockingQueue est une classe "thread-safe" de file d'attente bloquante de taille limitée.
$ java ExecutionSequentielle
public class CalculComplique implements Runnable { 1
private int x; CalculComplique(1) = 1539607551
public CalculComplique(int x) { this.x = x; } 4
public void run() { CalculComplique(4) = 1539607548
int y = x; q
for (int i=0; i< 100000; i++) $
for (int j=0; j< 100000; j++)
y += 5;
System.out.println("CalculComplique("+x+") = "+y); Une tâche a besoin d'un thread pour
} être exécutée.
}
Dans un programme strictement
séquentiel, c'est le thread main qui
public class ExecutionSequentielle { exécute les tâches les unes après des
public static void main (String [] args) { autres.
Scanner scanIn = new Scanner(System.in);
int nombre = 0; Solution valable car la tâche
while (scanIn.hasNextInt()) { CalculCompliquee ne fait que des
nombre = scanIn.nextInt(); calculs et n'est jamais en attente d'E/s
CalculComplique tache = new CalculComplique(nombre); ou de ressources.
tache.run(); Sinon, d'autres threads pourraient
} s'exécuter sur le(s) processeur(s)
}
pendant les attentes et blocages.
}
UnThreadParTache.java
CalculComplique.java
Thread (27/38) : comment exécuter des taches ?
Exécution parallèle "fanatique"
$ java UnThreadParTache
3
public class UnThreadParTache { 4
public static void main (String [] args) { CalculComplique(3) = 1539607549
Scanner scanIn = new Scanner(System.in); CalculComplique(4) = 1539607548
int nombre = 0; q
while (scanIn.hasNextInt()) { $
nombre = scanIn.nextInt();
Thread tache = new Thread(new CalculComplique(nombre));
tache.start();
}
} Solution valable dans le cas d'une charge modérée et de tâches faisant
} des E/S ou des attentes sur ressources : exemple serveur web.
Catastrophe si la charge augmente : temps de changement de contexte
de thread, consommation mémoire …
La classe Executors est une fabrique de pool de threads pour exécuter les tâches. La stratégie
newFixedThreadPool crée un pool de threads de taille fixe.
La classe ExecutorService :
La méthode submit() soumet une tâche à l'exécuteur qui le fera … ultérieurement selon sa stratégie.
La méthode shutdown() demande à l'exécuteur de se terminer quand toutes ses tâches déjà soumises
seront terminées.
La méthode shutdownNow() arrête l'exécuteur et tente d'annuler les tâches en cours.
….
MaitriseDesTaches.java
La méthode submit() de la classe ExecutorService renvoie un objet Future à la soumission d'une tâche.
La classe Future permet de suivre le cycle de vie d'une tâche : son état (attente, en exécution,
terminée, interrompue), son résultat s'il y a.
Calculcompliquee2.java
MaitriseDesTaches.java
L'interface Callable décrit une tâche fournissant un résultat ou propageant une exception par sa
méthode call().
class LongueTache2 implements Runnable { $ java GUIetTacheLongue2
private JProgressBar barreProgression;
public LongueTache2(JProgressBar bp) { barreProgression=bp; }
public void run() {
for(int i = 0; i < 100; i++ ) {
barreProgression.setValue(i); …........
System.out.print(".");
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
….......
}
System.out.println("") ;
}
}
ThreadSwing.java
Thread (33/38)
Thread de traitement des événements
class ThreadSwing extends JFrame {
public static void main(String args[]) {
System.out.println("main > thread name = "
2 threads pour l'application :
+ Thread.currentThread().getName()); Celui du main
new ThreadSwing(); Celui qui traite l'événement et le
} paint()
public ThreadSwing() {
this.setTitle("ThreadSwing");;
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton bouton = new JButton("bouton");
this.getContentPane().add(bouton);
bouton.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("actionPerformed > thread name = "
+ Thread.currentThread().getName());
}
}); $ java ThreadSwing
this.pack(); main > thread name = main
this.setVisible(true); paint > thread name = AWTEventQueue0
} paint > thread name = AWTEventQueue0
public void paint(Graphics g) { actionPerformed > thread name = AWTEventQueue0
super.paint(g);
System.out.println("paint > thread name = "
+ Thread.currentThread().getName());
}
}
Thread (34/38)
thread Event Dispatcher
Conséquences :
Le programmeur doit s'y plier pour tendre vers la
thread-safety.
C'est le pattern d'unicité de traitement graphique.
La bibliothèque swing offre des outils pour ce
faire.
Thread (35/38) GUIetTacheLongue3.java
SwingUtilities.invokeLater()
public class GUIetTacheLongue3 extends JFrame {
public static void main(String[] args) { La méthode invokeLater( ) de
SwingUtilities.invokeLater( new Runnable() { SwingUtilities planifie une tache qui
public void run() { new GUIetTacheLongue3(); } s'exécutera dans le thread de
}); traitement des événements.
} C'est une sorte d'Executor.
...
SwingUtilities possède d'autres
class LongueTache3 implements Runnable { méthodes.
private JProgressBar barreProgression;
public LongueTache3(JProgressBar bp) { barreProgression=bp; }
public void run() {
for(int i = 0; i < 100; i++ ) {
final int niveau = i;
SwingUtilities.invokeLater( new Runnable() {
public void run() { barreProgression.setValue(niveau); }
}); $ java GUIetTacheLongue3
System.out.print(".");
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
} …........
System.out.println("") ;
}
}
….......
En renvoyant la création du GUI du thread main au thread
EventDispatcher et en renvoyant la modification de la JProgressBar du
thread "longue tache" au thread EventDispatcher, le pattern d'unicité de
traitement graphique est respecté.
Thread (36/38) GUIetWorker1.java
SwingWorker
$ java GUIetWorker1
public class GUIetWorker1 extends JFrame {
public GUIetWorker1() {
...
final JLabel resultat = new JLabel(" ");
class CalculComplique6 extends SwingWorker<Integer, Void> {
private int x;
public CalculComplique6(int x) { this.x=x; }
public Integer doInBackground() {
int y = x;
for (int i=0; i< 100000; i++) La classe SwingWorker<T,V> réalise les workers
for (int j=0; j< 100000; j++) threads ou background threads. Elle implémente
y += 5; Future.
return y;
}
T est le type du résultat de la tache et V le type
protected void done() {
try { des résultats intermédiaires s'il y a.
resultat.setText(get().toString());
} catch (InterruptedException ie) { Sa tache est effectuée par la méthode
} catch (ExecutionException ee) {} doInBackground() qui retourne un résultat.
} Sa méthode done() est automatiquement lancée
} dans le thread EventDispatcher à la fin de la
this.getContentPane().add(resultat); tache.
this.pack();
Sa méthode get() retourne le résultat
this.setVisible(true);
CalculComplique6 calcul = new CalculComplique6(56);
et sinon l'attend. Elle lève les
calcul.execute(); exceptions "classiques".
}
}
Thread (37/38) GUIetWorker2.java
SwingWorker progression
...
final JProgressBar progressBar = new JProgressBar(0, 100);
$ java GUIetWorker2
class CalculComplique7 extends SwingWorker<Integer, Void> {
private int x;
public CalculComplique7(int x) { this.x=x; }
public Integer doInBackground() {
int y = x;
for (int i=0; i< 100000; i++) {
for (int j=0; j< 100000; j++)
y += 5; SwingWorker intègre des dispositifs de progression de la
setProgress(i/1000); tache en background.
} La méthode setProgress( ) affecte la propriété "liée" (bound
return y; property) : ce qui permet d'écouter ses changements en
} s'inscrivant comme PropertyChangeListener de la tache
protected void done() { worker.
try {
labelResult.setText("result ="+get().toString());
} catch (InterruptedException ie) {
} catch (ExecutionException ee) {}
}
}
CalculComplique7 calcul = new CalculComplique7(56);
calcul.addPropertyChangeListener(
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer)evt.getNewValue());
}
}
});
calcul.execute();
Thread (38/38) GUIetWorker3.java
SwingWorker annulation
...
boite.add(boutonAnnule);
$ java GUIetWorker3
class CalculComplique7 extends SwingWorker<Integer, Void> {
...
public Integer doInBackground() {
int y = x;
for (int i=0; (i<100000) && !isCancelled(); i++) {
for (int j=0; (j<100000) && !isCancelled(); j++)
y += 5;
setProgress(i/1000);
} SwingWorker intègre un dispositif
return y; d'annulation de la tache en
} background.
protected void done() {
try { La méthode isDone( ) retourne true
if (!isCancelled()) si la tache est accomplie.
labelResult.setText("result ="+get().toString()); La méthode cancel(force) tente
} catch (InterruptedException ie) {
d'annuler la tache : si force est
} catch (ExecutionException ee) {}
} true, alors l'annulation se fait même
} si la tache est en cours
... d'exécution.
boutonAnnule.addActionListener( La méthode isCancelled( ) retourne
new ActionListener() { true si la tache est annulée.
public void actionPerformed(ActionEvent ae) {
if (!calcul.isDone()) {
calcul.cancel(true);
labelResult.setText("annulé");
}
}
});