Cours

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 75

Programmation Objet 2 2éme partie

Didier FERMENT http://www.u-picardie.fr/ferment


Université de Picardie Jules Verne 2013-14
Après avoir étudié :
La programmation d'interface graphique

Nous allons aborder :


La programmation événementielle

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 :

Non traitées, elles RunTimeException ... ... OutOfMemoryError ...


plantent l'exécution Erreurs du programme
en cours. à prendre en compte Erreurs de la JVM qui
NumberformatException ... obligatoirement "plantent le programme
Erreurs du programme
Les Exceptions (2/8) Equation1.java
public class Equation1 {
  public static void main(String args[]) {
    int a = 0, b = 0;
    try {
RunTimeException
      a = Integer.parseInt(args[0]);
      b = Integer.parseInt(args[1]);
    } catch (NumberFormatException nfe) {
      System.err.println("équation entière ax+b=0 : les paramètres a et b
                          doivent être entier");
      System.exit(0);
    }
    Integer x  = resoudreEquation(a,b);
    System.out.println("résultat équation entière ax+b=0 : X = "+x);
  }
  private static Integer resoudreEquation(int a, int b) { Les RunTimeExceptions
    int sol = calculSolution(a,b); comme ArithmeticException
    return new Integer(sol); et NumberFormatException
  } peuvent être traitées ou non.
  private static int calculSolution(int a, int b) { Exemple de traitement :
    return b/a; try-catch
  }
} $ java Equation1 3 4
résultat équation entière ax+b=0 : X = 1
$ java Equation1 0 4
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Equation1.calculSolution(Equation1.java:19)
at Equation1.resoudreEquation(Equation1.java:15)
at Equation1.main(Equation1.java:11)
$ java Equation1 0 abc
équation entière ax+b=0 : les paramètres a et b doivent être entier
Equation2.java
Les Exceptions (3/8)
public class Equation2 {
  public static void main(String args[]) {
    ...
Try Catch
    try {
      Integer x  = resoudreEquation(a,b);
      System.out.println("résultat équation entière ax+b=0 : X = "+x);
    } catch (ArithmeticException ae) {
      System.err.println("équation entière ax+b=0 : erreur d\'éxécution : "
                         + ae.getMessage());
    }
  }
  private static Integer resoudreEquation(int a, int b) {
    int sol = calculSolution(a,b);
    return new Integer(sol);
  }
  private static int calculSolution(int a, int b) {
    return b/a;
  }
} $ java Equation2 0 4
équation entière ax+b=0 : erreur d'éxécution : / by zero

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

Sans l'instruction throws Exception :


$ javac Equation4Bad.java
Equation4Bad.java:28: unreported exception java.lang.Exception; must be caught or declared to be thrown
throw new Exception("division par 0");
^
1 error
Equation5.java
Les Exceptions (6/8)

 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.

Les IOExceptions sont à traiter obligatoirement.


Le schéma ci-dessus est à reprendre pour toute opération Open-Read/Write-Close sur des entrées-sorties.
Les Exceptions (8/8)

Pour aller plus loin ….

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);

L'autoboxing permet de transformer automatiquement une variable de type primitif


en un objet du type du wrapper correspondant.
L'unboxing est l'opération inverse.

Reste un problème : la liste est d'objet pas uniquement d'Integer !


D'où la généricité !
Types Génériques (1/13) Generic0.java

JAVA sans généricité


import java.util.*;
import java.io.*;
public class Generic0 {
  public static void main(String[] args) {  $ java7 Generic0
    ArrayList phrase = new ArrayList(); [il, fait, beau]
    phrase.add("il"); le 3eme Mot : beau
    phrase.add("fait"); [il, fait, beau, 3]
    phrase.add("beau");
    System.out.println(phrase.toString());
    String le3emeMot = (String) phrase.get(2);
    System.out.println("le 3eme Mot : "+le3emeMot);
    phrase.add(new Integer(3));
    System.out.println(phrase.toString());
   }  
}

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

Utiliser les classes "génériques"


public class Generic1 {
  public static void main(String[] args) { 
    ArrayList<String> phrase = new ArrayList<String>();
    phrase.add("il"); $ java7 Generic1
    phrase.add("fait"); [il, fait, beau]
    phrase.add("beau"); le 3eme Mot : beau
    System.out.println(phrase.toString());
    String le3emeMot = phrase.get(2);
    System.out.println("le 3eme Mot : "+le3emeMot);
   }   
} Utilisation de la classe "générique" : ArrayList<T>
appliquée au type String d'où ArrayList<String>
Inutile de faire de cast : ses méthodes renvoient le bon type.
Les vérifications se font à la compilation !
public class Generic2 {
  public static void main(String[] args) { 
    ArrayList<String> phrase = new ArrayList<String>();
    phrase.add("il");
    ...
    phrase.add(new Integer(3));
    ...
$ javac7 Generic2.java
Generic2.java:12: error: no suitable method found for add(Integer)
….1 error
Generic3.java
Types Génériques (3/13)

Utiliser les classes "génériques"


 
    ArrayList<String> phrase = new ArrayList<String>();
    phrase.add("il");
    phrase.add("fait");
$ java7 Generic3
    phrase.add("beau");
[il, fait, beau]
    System.out.println(phrase.toString());
[il, fait, beau]
    
[il, fait, beau]
    String[] t = {};
    String[] tab = phrase.toArray(t);
    
    List<String> phrase2 = Arrays.asList(tab);
    System.out.println(phrase2.toString());
    
    List<String> phrase3 = Arrays.asList("il", "fait", "beau");
    System.out.println(phrase3.toString());

Utilisation de la méthode toArray() de classe "générique" ArrayList<T>


public <T> T[ ] toArray(T[ ] tab) la méthode est générique : le paramètre tab ne sert qu'à fournir le type
Utilisation de la méthode static asList() de classe Arrays
public static <T> List<T> asList(T... a) la méthode est générique.

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

Convention de nommage pour les paramètres formels de type :


E - Element (de Collection)
K - Key
N - Number public class Triplet <T,S,U> {
T - Type   private T premier, S second, U troisieme;
V - Value
  public Triplet(T premier, S second, U troisieme)
S,U,V .... - 2, 3, 4éme types
  { ...
Paire.java
Types Génériques (5/13) UsePaire1.java

Déclarer une classe "générique"


public class Paire <T> {
  private T premier, second;
  public Paire(T premier, T second) {
    this.premier = premier; this.second = second;
  } $ java7 UsePaire1
 ... (1.2,4.6)

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());
...

Une classe générique doit être instanciée en passant/spécifiant le type.


Le type est n’importe quelle classe sauf :
les énumérations (car le type et les valeurs sont statiques)
les anonymous inner classes (car anonyme)
les exceptions (car c'est un mécanisme du runtime de la JVM)
UsePaire2.java
Types Génériques (6/13) UsePaire3.java

Diamond, inférence de type, raw type


public class UsePaire2 {
  public static void main(String[] args) { 
    Paire<Double> point1 = new Paire<Double>(1.2, 4.5);
    Paire<Double> point2 = new Paire<>(1.2, 4.5); 
    Paire p3 = point2 ;
    System.out.println(p3.toString());
    // Paire<> p4 ;   compilation :  error: illegal start of type
...
Le "diamond" <> évite de répéter le type passé en utilisant l'inférence de type pour le déterminer.
Paire est le "raw type", le type de base, la classe de base d'une déclaration générique. Exemple :
ArrayList est le raw type de ArrayList<T>

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

Type paramètre contraint


public class PaireOrdonnee <T extends Comparable> {
  private T premier, second;
  public PaireOrdonnee(T premier, T second) {
    if (premier.compareTo(second) < 0) {
      this.premier = premier;this.second = second;
    } else {
      this.premier = second;this.second = premier;
    }  
  } $ java UsePaireOrdonnee
  ...
PaireOrdonnee<Integer>(new Integer(4), new Integer(6))=(4,6)
PaireOrdonnee<String>("abc", "def")=(abc,def)

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)

Type paramètre wilcard $ java Collections


[1, 2, 3]
public class Collections {  [1, 2, 3]
  public static <T> void copy ( List<? super T> dest, 
                                List<? extends T> src) {  
      for (int i=0; i<src.size(); i++) 
        dest.add(src.get(i)); 
  } 
  public static void main(String[] args) { 
    ArrayList<Integer> nombres = new ArrayList<Integer>();
    nombres.add(1); nombres.add(2); nombres.add(3);
    System.out.println(nombres.toString());
    ArrayList<Number> copieNombres = new ArrayList<Number>();
    Collections.copy(copieNombres, nombres);
    System.out.println(copieNombres.toString());
  ...

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

La classe Collection<T> implémente l'interface Iterable<T>


(la classe correspondante est Iterator)
qui permet d'utiliser la boucle "foreach".
Sérialisation Clonage (0/13)
Objet

Attribut valeur 1 «m

Attribut valeur 2 bo is e
ut
à b n bin

»
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 classe ObjectOutputStream a une méthode writeObject() pour sérialiser un objet sérialisable : ce


n'est pas le cas de Puce1 !
Puce5.java
Sérialisation (2/13)
interface Serializable
import java.io.*; Implémenter l'interface Serializable
public class Puce5 implements Serializable { n'oblige à aucune implémentation de
  String nom = null; méthode !
  int nombrePattes = ­1; C'est une interface de marquage : elle
  public Puce5(String n, int nb) { sert à indiquer que l'opération de
    nom = n; nombrePattes = nb; sérialisation pré-existante est
  } autorisée.
  public String toString() {
    return "Puce nom : " + nom +", nombre de pattes = " + nombrePattes;
  }
} $ javac Puce5.java
$

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.

Ci-dessous la sérialisation de Puce5("pupuce", 4) :


$ od ­c sauvePuce 
0000000 254 355  \0 005   s   r  \0 005   P   u   c   e   5   n 274   (
0000020 312 241 332 337 330 002  \0 002   I  \0  \f   n   o   m   b   r
0000040   e   P   a   t   t   e   s   L  \0 003   n   o   m   t  \0 022
0000060   L   j   a   v   a   /   l   a   n   g   /   S   t   r   i   n
0000100   g   ;   x   p  \0  \0  \0 004   t  \0 006   p   u   p   u   c
0000120   e
0000121
Sérialisation (3/13) Puce5.java
Persistance5.java

Persistance dans un fichier


public class Persistance5 {
  public static void main(String[] args)  {
    try {
      Puce5 puce =  new Puce5("pupuce", 6);
      System.out.println("puce : "+puce.toString());
      FileOutputStream fos = new FileOutputStream("sauvePuce");
      ObjectOutputStream oos = new ObjectOutputStream(fos);
      oos.writeObject(puce);
      oos.close();
      puce = null;
      System.out.println("puce : "+puce);
      FileInputStream fis = new FileInputStream("sauvePuce");
      ObjectInputStream ois = new ObjectInputStream(fis);
      puce = (Puce5) ois.readObject();
      ois.close();
      System.out.println("puce : "+puce.toString());

   ...

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 < taille­1; ++i)
      res.append(tabElement[i]+", ");
    if (taille > 0)
      res.append(tabElement[taille­1]+" ");
    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)

Algorithme serialiser un objetA :



Foreach attribut :

Si type primitif

Alors opération prédéfinie

Sinon reférence d'objet

Si objet sérialisable

Alors récursiver sur objet

Sinon erreur

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);
...

Erreur normale : le mécanisme récursif de


sérialisation tombe sur un objet non sérialisable :
Puce1 .
Puce1.java
Clonage (9/13) Clonage1.java

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

clonage "en surface"

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

La classe Puce2 utilise en fait la méthode super.clone(),


donc le comportement par défaut :
en particulier, la copie simple des références d'objets.
La variable d'instance nom de l'objet et son clone sont donc la même référence.
Puce3.java
Clonage (12/13) Clonage3.java

clonage "en profondeur"

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

La classe Puce3 clone en profondeur :


l'objet référencé dans sa variable d'instance nom est dupliqué.
Le clonage en profondeur consiste à appliquer récursivement le clonage à
l'intérieur de l'objet … jusqu'à un certain niveau.

Remarque : la classe StringBuffer n'est pas Cloneable.


Clonage4.java
Clonage (13/13)
clonage "en profondeur"
public class Clonage4 {
  public static void main(String[] args)  {
    Puce3 puceA =  new Puce3("pupuce", 6);
    Puce3 puceB =  new Puce3("teigneuse", 5);
    SuperList lesPuces = new SuperList();
    lesPuces.add(puceA); lesPuces.add(puceB);
    SuperList encoreLesPuces = (SuperList) lesPuces.clone();
    lesPuces.remove(puceB);
    puceA.nom.reverse();
    System.out.println("lesPuces : "+lesPuces.toString());
    System.out.println("encoreLesPuces : "+encoreLesPuces.toString());
  }
}
class SuperList extends ArrayList<Puce3> {
  public Object clone() {
    SuperList copie = new SuperList(); La classe ArrayList clone "en surface".
    for (Puce3 puce : this) Elle est étendue en redéfinissant la
      copie.add((Puce3)puce.clone()); méthode clone() en "profondeur".
    return copie;
  }
}
...
$ java Clonage4
lesPuces : [Puce nom : ecupup, nombre de pattes = 6]
encoreLesPuces : [Puce nom : pupuce, nombre de pattes = 6, 
Puce nom : teigneuse, nombre de pattes = 5]
Moyenne.java
Thread (1/38) TestLent.java

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" ?

Solution avec 2 processus : 1 fenêtre "Shell" pour chacun


Autre solution puisqu'ils sont écrit dans le même langage : 2
class TestLent …. threads du même processus car le changement de contexte
  InputStream ins = null; de thread est moins long que celui de processus
  try {
    URL url = new URL("http://www.u­picardie.fr/ferment/chargement_lent.php");
    ins = url.openStream();
    BufferedReader buffReader = new BufferedReader(new InputStreamReader(ins));
    String ligne;
    while ((ligne = buffReader.readLine()) !=  null)
      System.out.println(ligne);
  }
  catch (MalformedURLException e) {} $ java TestLent 
  catch (IOException e) {}  chargement tres lent
  finally { chargement tres tres lent
    if (ins != null) chargement tres tres tres lent
      try { chargement tres tres tres tres lent
        ins.close(); chargement tres tres tres tres tres lent
      } catch (IOException e) {}  chargement tres tres tres tres tres tres lent
  } ...
Lent.java
Thread (2/38)
Runnable
public class Lent implements Runnable {
  public void run() {
    InputStream ins = null;
    try {
      URL url = new URL("http://www.u­picardie.fr/ferment/chargement_lent.php");
      ins = url.openStream();
      BufferedReader buffReader = new BufferedReader(new InputStreamReader(ins));
      String ligne;
      while ((ligne = buffReader.readLine()) !=  null)
        System.out.println(ligne);
    }
    catch (MalformedURLException e) {}
    catch (IOException e) {} 
    finally {
      if (ins != null)
        try {
          ins.close();
        } catch (IOException e) {} 
    }
  } Un thread, un fil d'exécution, peut être vu comme un "processus
} léger".
En Java, les objets à exécuter sur un fil d'exécution propre doivent
implémenter l'interface Runnable : la méthode run() sera exécutée
comme thread par la JVM avec sa propre pile d'exécution.

Remarque : toute application Java fonctionne avec au moins 1


processus qui exécute au moins 1 thread chargé de la méthode
main().
DeuxTachesParalleles.java
Thread (3/38) Lent.java

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[Thread­0,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.

Remarque : la classe Thread implémente l'interface Runnable et donne un moyen de (re)définir la


méthode run.
Risque de confusion entre Thread (classe, objet de ) et le thread fil d'exécution !
TroisTachesParalleles.java
Thread (5/38) Boucleinfinie.java
Dodo.java
Vie d'un thread
public class BoucleInfinie implements Runnable { public class Dodo implements Runnable {
  public void run() {   public void run()  {
    boolean encore = true;     try {
    int j = 1;       for (int i=0; i<20; i++) {
    while (encore) {         System.out.println("dodo");     
      for (int i=0; i<2000000000; ++i)         Thread.sleep(500);
        j = j*i ;       }
      System.out.println("....2000000000");      } catch (InterruptedException ie) { }
    }   }
  } }
} La vie de chaque thread s'organise
autour de 3 états principaux :
- en cours d'exécution
- ne peut pas s'exécuter (ex: sleep)
public class TroisTachesParalleles {
  public static void main (String [] args) {
- exécutable
    boolean encore = true; Un ordonnanceur de la JVM "arbitre"
    Scanner scanIn = new Scanner(System.in); entre les différents threads.
    Thread tacheDodo = new Thread(new Dodo());
    Thread tacheInfinie = new Thread(new BoucleInfinie()); La méthode sleep() suspend un
    tacheDodo.start(); thread pendant des millisecondes.
    tacheInfinie.start(); Cette méthode bloquante peut être
    while (encore) { interrompue par l’exception
      String frappe = scanIn.next(); InterruptedException.
      if (frappe.equals("q"))
         encore = false;
La frappe de "q" pour quitter n'arrête
    }
...     que le thread main. Il faut "killer" le
processus pour arrêter les 2 autres.
Thread (6/38)

Cycle de vie d'un thread


New Thread() start()

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

Fin du thread mais pas de l'objet


Arret1.java
Boucleinfinie.java
Thread (7/38) : Comment arrêter un thread ? Dodo.java
interrupt()

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

La méthode interrupt() "demande" au thread de s’arrêter ... ....2000000000


....2000000000
Ça fonctionne avec le thread "Dodo" mais pas avec le thread "boucleInfinie" . ....2000000000
....2000000000
Boucleinfinie2.java
Dodo2.java
Thread (8/38) : Comment arrêter un thread ? Arret2.java
isInterrupted()

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.

Attention à la méthode interrupted() :


elle renvoie aussi l'état "interrompu ou non du thread" et elle inverse l'état.
Elle sert souvent à effacer une demande d'interruption.

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 */}
  }
}

Lorsque une méthode interrupt() "demande" au thread de s’arrêter …

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) {
     …
   }
}

L'attente bloquante sur synchronized n'est pas interrompue par l'InterruptedException.


L'attente du verrou interne n'est pas pas garantie contre le risque de "famine" : la
spécification de la JVM n'impose rien sur le choix du thread à satisfaire lors de la libération du
verrou.
Interblocage.java
Thread (13/38) : Interblocage
par synchronized
public class Interblocage {
  ...
  private CompteBancaire compteA = new CompteBancaire("blingbling", 2000000000.0);
  private CompteBancaire compteB = new CompteBancaire("travailleurPlus", 1000.0);
  ...
    Thread redistrib1 = new Thread(new Redistribution(compteA, compteB));
    Thread redistrib2 = new Thread(new Redistribution(compteB, compteA));
    redistrib1.start();
    redistrib2.start();
    redistrib1.join();
    redistrib2.join();
    System.out.println("Fin travaillerPlusPourGagnerMoins");
  }
  class Redistribution implements Runnable {
    private CompteBancaire compte1;
    private CompteBancaire compte2;
    public Redistribution(CompteBancaire c1, CompteBancaire c2) {
      compte1 = c1; compte2 = c2;
    }
    public void run() { Pour faire des opérations sur les comptes, il faut bien
      for (int i=0; i<200; ++i) acquérir les verrous des comptes !
        synchronized(compte1) {
          if (compte1.solde > 10.0)
            synchronized(compte2) { $ java Interblocage 
              compte2.solde += 10.0; Fin travaillerPlusPour...
              compte1.solde ­= 10.0; $ java Interblocage 
            }          Fin travaillerPlusPour...
$ java Interblocage 
        }
Fin travaillerPlusPour...
    } $ java Interblocage
  } <ctrl>C
Interblocage.java
Thread (14/38)
trace de l'interblocage
$ java Interblocage 
Fin ravaillerPlusPourGagnerMoins Envoyer un SIGQUIT à la JVM
$ java Interblocage 
^\2012­07­05 12:20:56
pour l’arrêter avec trace :
<ctrl>\ ou kill -3 en Unix
Full thread dump OpenJDK Server VM (20.0­b11 mixed mode): <ctrl><break>

"Thread­1" 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)

"Thread­0" 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)

.... Eviter l'interblocage est la tache la plus difficile pour


programmer "thread-safe" …
Un moyen possible est fournit par la classe Lock avec sa
Found one Java­level deadlock: méthode tryLock() qui bloque selon un paramètre délai.
=============================
"Thread­1":
  waiting to lock monitor 0x08839e90 (object 0xa9b84900, a Interblocage$CompteBancaire),
  which is held by "Thread­0"
"Thread­0":
  waiting to lock monitor 0x08837050 (object 0xa9b84918, a Interblocage$CompteBancaire),
  which is held by "Thread­1"
...
Thread (15/38)
Autres risques de partage

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.

Dans le package java.util.concurrent :


AtomicNumber permet des opérations "atomiques thread-safe"
CountDownLatch est une barrière à 1 point/moment de synchronisation,
CyclicBarrier une barrière posant plusieurs points de synchronisation
Semaphore
ReentrantLock généralise les verrous avec des conditions de verrouillage
plus complexe , avec des méthodes "moins" bloquantes"
BlockingQueue et ConcurrentLinkedDeque proposent des techniques de
"production-consommation"
TestArrayList.java
TestVector.java
Thread (19/38) : Collections et Multithreading :
ArrayList Vector ...
import java.util.*;
public class TestArrayList {
  private ArrayList<String> maListe = new ArrayList<String>();
  public static void main (String [] args) throws InterruptedException {
    TestArrayList test = new TestArrayList();
    test.go(); $ java TestArrayList
  } maListe.size() : 400
  public void go() throws InterruptedException { $ java TestArrayList
    Thread tache1 = new Thread(new AccedeurListe(maListe)); maListe.size() : 400
    Thread tache2 = new Thread(new AccedeurListe(maListe)); …
    tache1.start(); $ java TestArrayList
    tache2.start(); maListe.size() : 398
    tache1.join(); $ java TestVector
monVector.size() : 400
    tache2.join();
$ java TestVector
    System.out.println("maListe.size() : "+maListe.size()); monVector.size() : 400
  } ...
  class AccedeurListe implements Runnable {
    private ArrayList<String> laListe;
    public AccedeurListe(ArrayList<String> l) {
      laListe = l;
    }
    public void run() { Les Collections du package java.util ne
      for (int i=0; i<200; ++i) sont pas "thread-safe", excepté Vector.
        laListe.add("abc");
    }
  }
} import java.util.*;
public class TestVector {
  private Vector<String> monVector = new Vector<String>();
  . . .
TestVector2.java
Thread (20/38) : Collections et Multithreading :
Vector
public class TestVector2 {
$ java TestVector2
  ...
monVector.getLast() : 
  public static String getLast(Vector<String> vect) { 3999­ième 
    int lastIndex = vect.size()­1; ...
    return vect.get(lastIndex); monVector.getLast() : 
  } 2000­ième 
  public static void deleteLast(Vector<String> vect) { $ java TestVector2
    int lastIndex = vect.size()­1; monVector.getLast() : 
    vect.remove(lastIndex); 3999­ième 
  } ...
  public void go() throws InterruptedException { monVector.getLast() : 
    Thread tache1 = new Thread(new AccedeurVector(monVector)); 2465­ième 
    Thread tache2 = new Thread(new AccedeurVector(monVector)); $
    for (int i=0; i<4000; ++i)
      monVector.add(i+"­ième ");
    tache1.start();
    tache2.start();
La classe Vector est "thread-safe"
    tache1.join();
    tache2.join();
donc (presque) chacune de ses
  } méthodes.
  class AccedeurVector implements Runnable {
    private Vector<String> leVector; Ça ne garantie pas que la
    public AccedeurVector(Vector<String> l) {leVector = l;} composition d'appel à ses
    public void run() { méthodes soit "thread-safe".
      for (int i=0; i<1000; ++i) {
        System.out.println("monVector.getLast() : " Obtenir le dernier et supprimer le
                            +getLast(monVector)); dernier ne sont pas des actions
        deleteLast(monVector); irréalistes !
      }
    }
  }
Thread (21/38) : Collections et Multithreading : TestVector3.java

Vector
public class TestVector3 {
  private Vector<String> monVector = new Vector<String>(); $ java TestVector3
  public static String getLast(Vector<String> vect) { monVector.getLast() : 
    synchronized(vect) { 3999­ième 
      int lastIndex = vect.size()­1; ...
      return vect.get(lastIndex); monVector.getLast() : 
    } 2000­iè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));
}

Si possible, un meilleur exemple :


Vector copieVecteur = null;
synchronized(vecteur) {
Le risque est qu'un élément enlevé   CopieVecteur = (Vector) vecteur.clone();
du vecteur original soit impacté par }
la "méthodeAFaire". for (int i=0; i<copieVecteur.size(); ++i)
  méthodeAFaire(copieVecteur.get(i));
}
Thread (22/38) : Collections et Multithreading : TestVector4.java

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() = 109­iè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.

C'est aussi un synchroniseur conçue selon le pattern producteur-consommateur

La méthode put() y dépose un élément ou attend si la file est pleine.


ExecutionSequentielle.java
CalculComplique.java
Thread (26/38) : comment exécuter des taches ?
Exécution séquentielle

$ 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 …

public class CalculComplique implements Runnable { Runnable s'avère pauvre pour


  private int x;
  public CalculComplique(int x) { this.x = x; }
implémenter les tâches :
  public void run() { pas de résultat à retourner
    int y = x; (directement),
    for (int i=0; i< 100000; i++) pas d'Exception à propager si la
      for (int j=0; j< 100000; j++) tâche échoue,
        y +=  5; pas d'information sur l'état
     System.out.println("CalculComplique("+x+") = "+y); d'avancement de la tâche.
  }
}
Thread (28/38) : comment exécuter des taches ?
Executor … de tâches ExecuteurDeTaches.java
CalculCompliquee.java
import java.util.concurrent.*;
public class ExecuteurDeTaches {
  public static void main (String [] args) {
    ExecutorService exec = Executors.newFixedThreadPool(2);
    Scanner scanIn = new Scanner(System.in);
    int nombre = 0;
    while (scanIn.hasNextInt()) {
      nombre = scanIn.nextInt();
      CalculComplique tache = new CalculComplique(nombre);
      exec.submit(tache);
    } $ java ExecuteurDeTaches
    exec.shutdown(); 3
  } 4
5
}
CalculComplique(3) = ­1539607549
CalculComplique(4) = ­1539607548
Dans le package java.util.concurrent, CalculComplique(5) = ­1539607547
autour de l'interface Executor, plusieurs classes permettent de: q
- gérer la soumission des tâches $
- gérer le cycle de vie des tâches.

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

Thread (29/38) : comment exécuter des taches ?


Future
$ java MaitriseDesTaches 

import java.util.concurrent.*; 4
public class MaitriseDesTaches { Somme de CalculComplique2 de 3 
  public static void main (String [] args)  et 4 = 1215752198
         throws InterruptedException, ExecutionException {
    ExecutorService exec = Executors.newSingleThreadExecutor();
    Scanner scanIn = new Scanner(System.in);
    int nombre1 = scanIn.nextInt();
    CalculComplique2 tache1 = new CalculComplique2(nombre1);
    Future<Integer> future1 = exec.submit(tache1);
    int nombre2 = scanIn.nextInt();
    CalculComplique2 tache2 = new CalculComplique2(nombre2);
    Future<Integer> future2 = exec.submit(tache2);
    int result = future1.get().intValue() + future1.get().intValue();
    System.out.println("Somme de CalculComplique2 de "+nombre1
                                       +" et "+nombre2+" = "+result);
    exec.shutdown();
  }
}

NewSingleThreadExecutor() correspond à la stratégie séquentielle de la classe Executors.

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

Thread (30/38) : comment exécuter des taches ?


Callable et Future
import java.util.concurrent.*;
public class CalculComplique2 implements Callable<Integer> 
{
  private int x;
  public CalculComplique2(int x) { this.x = x; }
  public Integer call() {
    int y = x;
    for (int i=0; i< 100000; i++)
      for (int j=0; j< 100000; j++)
        y +=  5;
    return y;
  }
}

L'interface Callable décrit une tâche fournissant un résultat ou propageant une exception par sa
méthode call().

La classe Future possède :


La méthode get() pour récupérer de résultat de la tache, et attend si elle n'est pas terminée. Elle peut
lancer l'InterruptedException et l'ExecutionException si la tâche "crash".
La méthode cancel() pour tenter d’arrêter la tache.
La méthode isDone() pour savoir si la tache est finie sans probléme, et sinon isCancelled().
Thread (31/38) GUIetTacheLongue1.java

Thread et Application Graphique


public class GUIetTacheLongue1 extends JFrame { $ java GUIetTacheLongue1
  JProgressBar barreProgression; 
  public static void main(String[] args) {
    new GUIetTacheLongue1();   }
  public GUIetTacheLongue1() {
    … ….............
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); …..
    barreProgression = new JProgressBar();  
    barreProgression.setMinimum(0);
    barreProgression.setMaximum(99);
    barreProgression.setValue(0);    
    JButton bouton = new JButton("Demarrer la tache longue");
    bouton.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent e) { Un bouton permet de déclencher
          for(int i = 0; i < 100; i++ ) {
            barreProgression.setValue(i);
la tache longue.
            System.out.print("."); Le traçage en console indique
            try { bien la progression mais pas en
              Thread.sleep(100); GUI.
            } catch (InterruptedException ie) {} La progression ne s'affiche en GUI
          } que quand elle est finie !
          System.out.println("") ;         De plus, l'arrêt en cliquant sur le X
        } de la frame ne fonctionne pas.
      } Par contre, le <ctrl>C en console
    );   fonctionne.
    pack();
    setVisible(true);
  }
}
Thread (32/38) GUIetTacheLongue2.java

Thread et Application Graphique


public class GUIetTacheLongue2 extends JFrame {
  ...  
    JButton bouton = new JButton("Demarrer la tache longue");
    bouton.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          Thread tache = new Thread(new LongueTache2(barreProgression));
          tache.start();  
        }
      }
    );  
Le traitement de l'événement consiste à lancer un
    pack(); autre thread de traitement de la longue tache.
    setVisible(true); Cela semble bien fonctionner ….
  } En fait, il y a un risque d’interblocage !
}

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 = AWT­EventQueue­0
  } paint ­­­­­> thread name = AWT­EventQueue­0
  public void paint(Graphics g) { actionPerformed ­­­­­> thread name = AWT­EventQueue­0
    super.paint(g);  
    System.out.println("paint ­­­­­> thread name = " 
                              + Thread.currentThread().getName());  
  }
}
Thread (34/38)
thread Event Dispatcher

Tous les "frameworks" graphiques sont


(malheureusement) monothread à cause :
De la complexité des bibliothèques
De la sensibilité aux interblocages, notamment du
au MVC qui laisse une grande liberté/souplesse
au programmeur
Pour Swing, du fait que les composants
graphiques ne sont pas thread-safe.

Globalement, la thread-safety d'un GUI est basé


sur le traitement monothread des événements,
des rafraîchissements (paint) et des modifications
des composants.

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é");
        }
      }
  });

Vous aimerez peut-être aussi