voisin-polian : introduction à java 1 introduction à java - les « threads » - frédéric voisin...
TRANSCRIPT
Voisin-Polian : Introduction à Java 1
Introduction à Java
- les « Threads » -
Frédéric VOISIN – Nicole POLIAN
FIIFO - « Remise à Niveau »
Voisin-Polian : Introduction à Java 2
Les « Threads » Java (ou « processus légers »)
Ils permettent de lancer plusieurs tâches en même temps au sein d’un même processus
Ils permettent de refléter des découpages logiques en composants s’exécutant indépendamment ou en coopération
Ils sont utilisés en interne par Java : le ramasse-miette est un thread de faible priorité, la méthode main(String[] args) s’exécute dans un thread …
Les threads partagent la même zone mémoire, ce qui facilite la communication entre threads. Le temps de commutation entre threads est inférieur à celui des processus.
Voisin-Polian : Introduction à Java 3
Exemple (presque) sans thread
public class Train { int vitesse; String nom; public Train(int v, String n){ vitesse = v; nom = n;} public void run() { System.out.println(nom + " part"); try { Thread.currentThread().sleep(vitesse*500); } catch (InterruptedException e) {} System.out.println(nom + " roule"); try { Thread.currentThread().sleep(vitesse*50); } catch (InterruptedException e) {} System.out.println(nom + " s'arrete"); }}
La méthode main s’exécute dans un thread !Le déroulement de sleep pourrait être abrégé par une « interruption », d’où le try/catch
Voisin-Polian : Introduction à Java 4
Exemple sans thread (suite)
// lancement de trois trains de vitesses différentes
public class TestTrain { public static void main (String[] args) { Train micheline = new Train(50, "MICHELINE"); Train tgv = new Train(10, "TGV"); Train corail = new Train(20, "CORAIL");
micheline.run(); tgv.run (); corail.run(); System.out.println("fin du main"); }}
MICHELINE part MICHELINE roule MICHELINE s'arrete TGV part TGV roule TGV s'arrete CORAIL part CORAIL roule CORAIL s'arrete fin du main
Voisin-Polian : Introduction à Java 5
Exemple avec thread
public class Test { public static void main(String[] args) { Train tgv = new Train(10, "TGV"); Train corail = new Train(20,"CORAIL"); Train micheline = new train (50, "MICHELINE"); micheline.start(); tgv.start(); corail.start(); System.out.println("Threads actifs : " + Thread.currentThread().activeCount());
try { corail.join(); // attendre la fin de corail ! } catch (InterruptedException ex) { } System.out.println("Threads actifs : " + Thread.currentThread().activeCount());
System.out.println("fin du main"); }}
Voisin-Polian : Introduction à Java 6
Exemple avec thread (suite)
class Train extends Thread { int vitesse; public Train(int v, String n) { super(n); // permet de « nommer » le thread. // getName() renvoie le nom ! vitesse = v; } public void run() { System.out.println(getName() + " part"); try { sleep(vitesse*500); } catch(InterruptedException e) { } System.out.println(getName() + " roule"); try { sleep(vitesse*50); } catch(InterruptedException e) { } System.out.println(getName() + " s'arrete"); }}
Exemple d’exécution
Threads actifs : 4MICHELINE partTGV partCORAIL partTGV roule TGV s'arreteCORAIL roule CORAIL s'arreteThreads actifs : 2 fin du mainMICHELINE roule MICHELINE s'arrete
Voisin-Polian : Introduction à Java 7
Le principe des Threads
La classe Train dérive de la classe Thread.
La méthode start démarre le thread, ce qui entraîne l’appel de la méthode
run. Le thread termine quand il atteint la fin de la méthode run.
La méthode join permet de se synchroniser avec la fin d’un thread.
Une autre synchronisation serait nécessaire si le thread produisait des résultats à récupérer (à voir après)
L’héritage multiple n’existant pas en Java, il existe un deuxième mécanisme pour utiliser les threads : l’utilisation de l’interface Runnable, qui impose l’existence d’une méthode run. On passe alors une instance d’une classe qui implémente Runnable comme premier argument au constructeur de Thread.
Voisin-Polian : Introduction à Java 8
Utilisation de l’interface Runnable
public class Test {
public static void main(String[] args) {Thread tgv = new Thread(new Train(10, "TGV"));
Thread corail = new Thread(new Train(20, "CORAIL"));
Thread micheline = new Thread(new Train(50, "MICHELINE"));
micheline.start(); tgv.start(); corail.start();
System.out.println("fin du main") ;
}
}
// le paramètre du constructeur de Thread doit implémenter l’interface Runnable !
Voisin-Polian : Introduction à Java 9
Utilisation de l’interface Runnable (suite)
public class Train implements Runnable {int v; String nom;public Train(int i, String n) { v = i; nom = n;}public void run() { System.out.println(nom + " part"); try { Thread.currentThread().sleep(v*500); } catch(InterruptedException e) {} System.out.println(nom + " roule"); try { Thread.currentThread().sleep(v*50); } catch(InterruptedException e) {} System.out.println(nom + " s'arrete");}
}
fin du main
MICHELINE part
TGV part
CORAIL part
TGV roule
TGV s'arrete
CORAIL roule
CORAIL s'arrete
MICHELINE roule
MICHELINE s'arrete
Voisin-Polian : Introduction à Java 10
Les états d’un Thread
créé
actifEn attente
mort
new ( )
terminaison
start ( )
wait ( ) ou sleep ( )
notify ( ) ou interruption
Voisin-Polian : Introduction à Java 11
Les principales méthodes liées aux threads
statiques : currentThread()activeCount() // threads démarrés et pas encore mortsenumerate() // permet de les copier dans un tableau
// Thread[] th=new Thread[nb];nb=Thread.enumerate(th);yield() // rend la main volontairement pour un autre threadsleep() // ne relâche pas les moniteurs (cf. infra)
non statiquesisAlive()join()setDaemon() // à faire avant start() !isDaemon()
dans Object // pour les moniteurs (synchronisation de threads)wait // en attente de notificationnotify // notification
« Deprecated » (induisent de forts risques de deadlock, cf. infra)stop, suspend, resume
Voisin-Polian : Introduction à Java 12
Exemple de Thread
public class Heure1 extends Frame { // utilisation de AWT !!
Label l1, l2, h1, h2; Button arret1; Timer1 t1, t2;
Heure1() {
setLayout(new FlowLayout());
l1 = new Label (" ");
l2 = new Label (" ");
l1.setBackground(Color.white); l2.setBackground(Color.white);
l1.setAlignment(Label.CENTER); l2.setAlignment(Label.CENTER);
Font f = new Font("TimesRoman", Font.PLAIN, 18); setFont(f);
arret1 = new Button ("STOP");
h1 = new Label("MONTREAL"); h2 = new Label("PARIS");
ActionListener larret1 = new ActionListener() {
public void actionPerformed(ActionEvent e){ System.exit(1);}
}; // classe interne, définie au vol !
… // ActionListener est une interface associée aux événements click sur les boutons
Voisin-Polian : Introduction à Java 13
Exemple de Thread (suite)
arret1.addActionListener(larret1); // associe action/bouton add(arret1); // ajout des composants add(l1); add(h1); // à l’instance de Frame add(l2); add(h2); t1 = new Timer1(l1, 1); t2 = new Timer1(l2, 2); t1.start(); t2.start(); } // fin de Heure1()
public static void main(String[] args) { Heure1 t = new Heure1(); t.setBounds(20, 20, 800, 60); t.setVisible(true);}
}
Voisin-Polian : Introduction à Java 14
Exemple de Thread (suite)
public class Timer1 extends Thread {Label l; int type; Date maDate;SimpleDateFormat mF = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
public Timer1(Label l, int t) { this.l = l; type = t; }
public void run() { while (true) { try { sleep (2000); } catch(InterruptedException e) {} maDate = new Date(); if (type == 1) { mF.setTimeZone(TimeZone.getTimeZone("America/Montreal")); L.setText(mF.format(maDate)); } else { mF.setTimeZone(TimeZone.getTimeZone("Europe/Paris")); L.setText(mF.format(maDate)); } }
}
Voisin-Polian : Introduction à Java 15
Les démons (« daemon »)
Il existe deux sortes de threads les threads ordinaires (utilisateurs) les threads démons (notamment créés par la JVM; exemple : le ramasse-miettes)
deux méthodes setDaemon(boolean b) // à exécuter avant start boolean isDaemon()
Un « processus » Java s’arrête lorsqu’il ne reste plus que des threads démons. Ceux-ci correspondent à des threads « de service » qui ne contribuent pas directement à la logique applicative.
Voisin-Polian : Introduction à Java 16
L’ordonnancement (scheduling)
Question : quel thread a la main ?celui qui a la plus grande priorité parmi les threads actifs !
pour changer la priorité : getPriority() setPriority()
Thread.MIN_PRIORITY Thread.MAX_PRIORITY Thread.NORM_PRIORITY
En cas d’égalité, certaines implémentation laissent la main au thread en cours (pas d’équité entre les threads potentiellement activables).
Dans ce cas, il faut utiliser la méthode yield() ou bien sleep(0) pour relâcher la main.
Voisin-Polian : Introduction à Java 17
Arrêter un Thread
public class TestArret { public static void main(String[] args) { tt = new Train(); tt.start(); try { Thread.currentThread().sleep(2000); tt.stoppe(); tt.join(); } catch (InterruptedException ex) {} System.out.println("fin du main"); }
} class Train extends Thread { private boolean arrete = false; public void run() { while (! arrete) { System.out.println("le train roule"); try { sleep(500);} catch(InterruptedException e) {} } System.out.println("le train s'arrete"); } public void stoppe() { arrete = true ; }
} // accès sans précaution à arrete ;-(
le train roule
le train roule
le train roule
le train roule
le train s'arrete
fin du main
Voisin-Polian : Introduction à Java 18
Problèmes de synchronisation
Les threads partagent les mêmes données et agissent sur les mêmes objets problèmes d’accès incohérents aux données
On peut protéger une suite d’instruction (ou une fonction) avec synchronized
Pour une fonction : à tout instant il s’exécute au plus une fonction synchronized sur un objet donné… mais les fonctions non synchronized n’ont aucune obligation synchronized int fonct() { …}
Par « verrouillage » d’un objet (arbitraire) pour une suite d’instructions arbitraire :synchronized (obj) { // au plus une séquence d’instructions ou méthode
// synchronized à la fois sur obj. obj.action(); … }
Reprend les principes des moniteurs de Hoare/Brinch Hansen
Voisin-Polian : Introduction à Java 19
Exemple de synchronisation
class Truc {int val = 0;void incr(int tempo) { int i = val; try { Thread.currentThread().sleep(tempo); } catch (InterruptedException e) {} val = i+1; // la valeur de val il y a un certain temps ! try { Thread.currentThread().sleep(tempo) ; } catch (InterruptedException ex) { } ; }public String toString() { return "valeur " + val;}}
class Collision {static truc cpt = new truc();public static void main(String[] arg) { Compteur t1 = new Compteur(cpt,1); Compteur t2 = new Compteur(cpt,2); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException ex) {}; System.out.println("fin du main : " + cpt); }}
Voisin-Polian : Introduction à Java 20
Exemple de synchronisation (suite)
class Compteur extends Thread {int num; truc cpt; Random rd;public Compteur (truc c, int nm) { num = nm; cpt = c; rd = new Random(nm);}
public void run() { System.out.println("debut run " + num); for(int i = 0; i<100; i++) { // synchronized(cpt) { cpt.incr(Math.abs(rd.nextInt())); } } System.out.println ("fin run " + num);}
}
sans synchronized(cpt) debut run 1 debut run 2 fin run 1 fin run 2 fin du main : valeur 143
avec synchronized(cpt) debut run 1 debut run 2 fin run 1 fin run 2 fin du main : valeur 200
Voisin-Polian : Introduction à Java 21
Les appels wait et notify
wait bloque l’exécution du thread jusqu’à réception d’une « notification » envoyée par un autre thread. Le verrou sur l’objet est levé (à la différence de sleep !)
notify envoie la notification précédente : « réveil » du (ou plutôt d’un) thread bloqué. notifyAll() réveille tous les threads bloqués sur cet objet.
Attention: les threads débloqués doivent encore « acquérir » l’objet sur lequel ils se synchronisent. Ils sont en concurrence avec tous les autres threads similaires (pas de privilège d’accès). Souvent on doit englober le wait dans une boucle d’accès en attendant qu’une certaine condition soit effectivement réalisée.
Attention aux deadlocks, si on acquiert les objets dans le désordre ou si on oublie d’en libérer l’accès !
Voisin-Polian : Introduction à Java 22
wait et notify (suite)
synchronized ( obj ) {
obj.wait ( ) ;
}
synchronized ( obj ) {
obj.notify ( ) ;
}
T1..
T2..
.
.
.
.
.
.
réveil
L’attente se fait « sur » un objet. Le notify est envoyé à ce même objet.
L’objet doit « appartenir » au thread qui exécute wait/notify, cela implique l’utilisation obligatoire de synchronized. Gérer le verrou sur un objet nécessite d’acquérir cet objet !
Voisin-Polian : Introduction à Java 23
Un Exemple
public class Heure2 extends Frame { Label l1, l2; Button h1, h2, arret1; Timer2 t1, t2;
Heure2() { setLayout(new FlowLayout()); l1 = new Label(" "); l2 = new Label(" "); l1.setBackground(Color.white); l2.setBackground(Color.white); l1.setAlignment(Label.CENTER); l2.setAlignment(Label.CENTER); Font f = new Font("TimesRoman", Font.PLAIN, 18); setFont(f); arret1 = new Button("Stop Montreal"); h1 = new Button("MONTREAL"); h2 = new Button("PARIS"); ActionListener larret1 = new ActionListener() { public void actionPerformed(ActionEvent e) { t1.stoppe(); // arret definitif de l'horloge locale remove(l1); remove(h1); remove(arret); }};…
Voisin-Polian : Introduction à Java 24
Un Exemple (suite)
ActionListener lh2 = new ActionListener() { public void actionPerformed(ActionEvent e) { if (! t2.isAlive()) { t2.start(); } }} ; ActionListener lh1 = new ActionListener() { public void actionPerformed(ActionEvent e) { if (! t1.isAlive()) { t1.start(); } else { t1.bascule(); } }};
arret1.addActionListener(larret1); h1.addActionListener(lh1; h2.addActionListener(lh2); add(arret1); add(l1); add(h1); add(l2); add(h2); t1 = new Timer2(l1, 1); t2 = new Timer2(lab2, 2);}public static void main(String[] arg) { Heure2 t = new Heure2(); t.setBounds(20, 20, 800, 60); t.setVisible(true);}
}
Voisin-Polian : Introduction à Java 25
Un Exemple (suite)
class Timer2 extends Thread { Label L; int type; boolean marche, actif;
public Timer2(Label l, int t) { L = l; type = t; marche = true; actif = true; }
public void stoppe() { marche = false; }
public void bascule() { if (actif) { actif = false ; } else { synchronized(L) { L.notify(); } } }
Voisin-Polian : Introduction à Java 26
Un Exemple (suite)
public void run() { Date maDate; SimpleDateFormat mF=new SimpleDateFormat("dd-MMM-yyyyHH:mm:ss"); while (marche) { try { sleep (2000); } catch (InterruptedException ex) {} if (! actif) { try { synchronized(L) { L.wait(); } } catch (InterruptedException ex) {} actif = true ; } maDate = new Date(); if (type == 1){ mF.setTimeZone(TimeZone.getTimeZone("America/Montreal")); L.setText(monFormat.format(maDate)); } else { mF.setTimeZone(TimeZone.getTimeZone("Europe/Paris")); L.setText(mF.format(maDate)); } }}}
Voisin-Polian : Introduction à Java 27
Un Exemple (affichage)