les Threads

Problématique : Le perroquet et moi souhaitons bavarder en même temps

Source de BavarderEtLancerLePerroquet0.java
class BavarderEtLancerLePerroquet0
{ 
  public static void main(String args[]) {
    Perroquet0 perroquet = new Perroquet0("coco",4);
    blabla();
    blabla();
    perroquet.run();
    blabla();
    for (int n=0; n<10; n++) {
      try { 
        Thread.sleep((int)(Math.random()*1000)); 
      catch(InterruptedException e) {
        System.out.println(e.getMessage());
        System.exit(1);
      }
      blabla();
    }
  }
  private static void blabla() {
    System.out.println("blabla");
  }
}

class Perroquet0 
{
  private String cri = null;
  private int fois = 0;
  public Perroquet0(String s, int i)   { 
    cri = s;
    fois = i;
  }
  /** methode de perroquet 
  * qui pourrait se nommer repeter
  */    
  public void run(){
    for (int n=0; n<fois; n++)  {
      try { 
        Thread.sleep((int)(Math.random()*1000)); 
      }
      catch(InterruptedException e) {
        System.out.println(e.getMessage());
        System.exit(1);
      }
      System.out.println(cri);
    }
  }
}

  • EXECUTION
    blabla
    blabla
    coco
    coco
    coco
    coco
    blabla
    blabla
    blabla
    blabla
    blabla
    blabla
    blabla
    blabla
    blabla
    blabla
    blabla
  • Les lignes de la classe :
    • Malheureusement, le perroquet interrompt le bavardeur qui ne "blablate" qu'après que le perroquet ait fini.
    • Ce que nous voudrions :
      • le bavardeur "blablate"
      • puis démarre le perroquet
      • puis "blablater" en même temps que le perroquet se répète

Multitâche

Source de BavarderEtLancerLePerroquet1.java
class BavarderEtLancerLePerroquet1
{ 
  public static void main(String args[]) {
   class BavarderEtLancerLePerroquet1
{ 
  public static void main(String args[]) {
    Perroquet1 objectPerroquet = new Perroquet1("coco",10);
    Thread threadPerroquet = new Thread(objectPerroquet);
    threadPerroquet.start();
    for (int n=0; n<10; n++) {
      try { 
        Thread.sleep((int)(Math.random()*1000));
      }
      catch(InterruptedException e) { }
      blabla();
    }
  }
  private static void blabla() {
    System.out.println("blabla");
  }
}

class Perroquet1 implements Runnable
{
  private String cri = null;
  private int fois = 0;
  public Perroquet1(String s, int i)   { 
    cri = s;
    fois = i;
  }
  public void run(){
    for (int n=0; n<fois; n++) {
      try { 
        Thread.sleep((int)(Math.random()*1000));
      }
      catch(InterruptedException e) { }
      System.out.println(cri);
    }
  }
}

Thread

Source de BavarderEtLancerLePerroquet2.java
class BavarderEtLancerLePerroquet2
{ 
  public static void main(String args[]) {
    Perroquet2 perroquet = new Perroquet2("coco",10);
    perroquet.start();
    for (int n=0; n<10; n++) {
      try { 
        Thread.sleep((int)(Math.random()*1000));
      }
      catch(InterruptedException e) {}
      blabla();
    }
  }
  private static void blabla() {
    System.out.println("blabla");
  }
}

class Perroquet2 extends Thread
{
  private String cri = null;
  private int fois = 0;
  public Perroquet2(String s, int i)   { 
    cri = s;
    fois = i;
  }
  public void run(){
    for (int n=0; n<fois; n++) {
      try { 
        Thread.sleep((int)(Math.random()*1000));
      }
      catch(InterruptedException e) {}
      System.out.println(cri);
    }
  }
}

Cycle de vie d'un thread

Source de BavarderEtLancerLePerroquet4.java
class BavarderEtLancerLePerroquet4
{ 
  public static void main(String args[]) {
    Perroquet4 perroquet = new Perroquet4("coco",5);
    perroquet.start();
    System.out.println("thread bavard : "
          + Thread.currentThread().getName());
    for (int n=0; n<15; n++) {
      try { 
        Thread.sleep((int)(Math.random()*1000));
      }
      catch(InterruptedException e) { }
      blabla();
    }
    System.out.println("thread perroquet isAlive : "
        + perroquet.isAlive());
  }
  private static void blabla() {
    System.out.println("blabla");
  }
}

class Perroquet4 extends Thread
{
  private String cri = null;
  private int fois = 0;
  public Perroquet4(String s, int i)   { 
    cri = s;
    fois = i;
  }
  public void run(){
    System.out.println("thread perroquet : "
          + Thread.currentThread().getName());
    for (int n=0; n<fois; n++) {
      try { 
        Thread.sleep((int)(Math.random()*1000));
      }
      catch(InterruptedException e) { }
      System.out.println(cri);
    }
  }
}

Les propriétés des différents threads

Source de BavarderEtLancerLePerroquet5.java
class BavarderEtLancerLePerroquet5
{ 
  public static void main(String args[]) {
    Thread.currentThread().setName("bavard");
    Perroquet5 perroquet = new Perroquet5("coco",15);
    perroquet.start();
    for (int n=0; n<5; n++) {
      try { 
        Thread.sleep((int)(Math.random()*1000));
      }
      catch(InterruptedException e) { }
      blabla();
    }
  }
  private static void blabla() {
    System.out.println("blabla");
  }
}

class Perroquet5 extends Thread
{
  private String cri = null;
  private int fois = 0;
  public Perroquet5(String s, int i)   { 
    super("perroquet");
    cri = s;
    fois = i;
  }
  public void run(){
    afficheThreads();
    for (int n=0; n<fois; n++) {
      try { 
        Thread.sleep((int)(Math.random()*1000));
      }
      catch(InterruptedException e) { }
      System.out.println(cri);
    }
    afficheThreads();
  }
  private void afficheThreads() {
    Thread[] tabThread = new Thread[Thread.activeCount()];
    int nbrThread = Thread.enumerate(tabThread);
    for (int i = 0; i < nbrThread ; i++)
      System.out.println(i + "-ieme thread : " 
                 + tabThread[i].getName());
  }
}
EXECUTION
0-ieme thread : bavard
1-ieme thread : perroquet
blabla
blabla
coco
blabla
coco
blabla
blabla
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
0-ieme thread : perroquet
1-ieme thread : DestroyJavaVM
  • les lignes de la classe :
    • la méthode setName(String nom) permet de nommer le thread
      • le constructeur offre la possibilité de le nommer ainsi Thread(String nom)
    • la méthode de classe activeCount() donne le nombre de threads actifs dans le groupe de l'appelant.
    • la méthode de classe enumerate(Thread[] tableau) stocke dans le tableau donné les références des threads actifs dans le groupe de l'appelant et ses sous-groupes. Elle renvoie le nombre de threads actifs obtenus.
  • remarquons que le thread main, renommé bavard, a fini avant le thread perroquet, et ne l'attend pas pour terminer.

Synchronisation sur terminaison

Source de BavarderEtLancerLePerroquet6.java
class BavarderEtLancerLePerroquet6
{ 
  public static void main(String args[]) {
    Perroquet6 perroquet = new Perroquet6("coco",10);
    perroquet.start();
    for (int n=0; n<5; n++) 
      blabla();
    try { 
      perroquet.join();
    }
    catch(InterruptedException e) {
      System.out.println(e.getMessage());
      System.exit(2);
    }
    System.out.println("fin du thread perroquet !");
    for (int n=0; n<5; n++) 
      blabla();     
  }
  private static void blabla() {
    System.out.println("blabla");
    try { 
      Thread.sleep((int)(Math.random()*1000));
    }
    catch(InterruptedException e) { }
  }
}

class Perroquet6 extends Thread
{
  private String cri = null;
  private int fois = 0;
  public Perroquet6(String s, int i)   { 
    super("perroquet");
    cri = s;
    fois = i;
  }
  public void repeter() {
    System.out.println(cri);
    try { 
      Thread.sleep((int)(Math.random()*1000));
    }
    catch(InterruptedException e) { }
  }
  public void run() {
    for (int n=0; n<fois; n++) 
      repeter();
  }
}
EXECUTION
blabla
coco
coco
blabla
blabla
blabla
coco
coco
blabla
coco
coco
coco
coco
coco
coco
fin du thread perroquet !
blabla
blabla
blabla
blabla
blabla
  • les lignes de la classe :
    • la méthode join() attend la terminaison du thread spécifié.
      • si le thread est créé mais pas "starté", il est considéré comme terminé !
      • join peut aussi avoir un paramètre donné qui est un timeout maximal.
  • Les 2 threads ne sont plus indépendants puisque le "main" attend, à une certaine étape, la terminaison (fin d'exécution) de l'autre. C'est une forme de synchronisation.

Priorités

Le bavardeur et le Perroquet sans sleep

Source de BavarderEtLancerLePerroquet7.java
class BavarderEtLancerLePerroquet7
{ 
  public static void main(String args[]) {
    Perroquet7 perroquet = new Perroquet7("coco",10);
    perroquet.start();
    for (int n=0; n<10; n++) 
      blabla();   
  }
  private static void blabla() {
    System.out.println("blabla");
  }
}

class Perroquet7 extends Thread
{
  private String cri = null;
  private int fois = 0;
  public Perroquet7(String s, int i)   { 
    cri = s;
    fois = i;
  }
  public void repeter() {
    System.out.println(cri);
  }
  public void run() {
    for (int n=0; n<fois; n++) 
      repeter();
  }
}
EXECUTION
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
blabla
blabla
blabla
blabla
blabla
blabla
blabla
blabla
blabla
blabla
  • le temps d'exécution est trop court pour visualiser la répartition du CPU entre les 2 threads.

Source de CourseInfernale1.java
public class CourseInfernale {  
  public static void main(String[] args) {
    Coureur hinaut = new Coureur("Hinaut");
    Coureur poulidor = new Coureur("Poulidor");    
    hinaut.start();
    poulidor.start();
  }
}
class Coureur extends Thread {
  String nom;
  public Coureur(String nom) {
    super(nom);
    this.nom = nom;
  }
  public void run() {
    long nombreFoulee  = 0;
    while (nombreFoulee < 5000000L) {
      nombreFoulee++;
      if ((nombreFoulee % 500000L) == 0) {
        System.out.println("Coureur " + nom 
        + " a couru  " + nombreFoulee + " foulees");
      }
    }
  }
}
EXECUTION
Coureur Hinaut a donne 500000 coups de pedale.
Coureur Poulidor a donne 500000 coups de pedale.
Coureur Hinaut a donne 1000000 coups de pedale.
Coureur Poulidor a donne 1000000 coups de pedale.
Coureur Hinaut a donne 1500000 coups de pedale.
Coureur Poulidor a donne 1500000 coups de pedale.
Coureur Hinaut a donne 2000000 coups de pedale.
Coureur Poulidor a donne 2000000 coups de pedale.
Coureur Poulidor a donne 2500000 coups de pedale.
Coureur Hinaut a donne 2500000 coups de pedale.
Coureur Hinaut a donne 3000000 coups de pedale.
Coureur Poulidor a donne 3000000 coups de pedale.
Coureur Hinaut a donne 3500000 coups de pedale.
Coureur Poulidor a donne 3500000 coups de pedale.
Coureur Hinaut a donne 4000000 coups de pedale.
Coureur Poulidor a donne 4000000 coups de pedale.
Coureur Hinaut a donne 4500000 coups de pedale.
Coureur Poulidor a donne 4500000 coups de pedale.
Coureur Hinaut a donne 5000000 coups de pedale.
Coureur Poulidor a donne 5000000 coups de pedale.
  • Java n'impose pas que le système soit "time-sliced" : cad que la même quantité de temps soit impartie aux threads de même niveau de priorité

Source de CourseInfernale2.java
public class CourseInfernale2 {  
  public static void main(String[] args) {
    Coureur hinaut = new Coureur("Hinaut");
    Coureur poulidor = new Coureur("Poulidor");    
    hinaut.setPriority(Thread.MAX_PRIORITY);
    poulidor.setPriority(Thread.MIN_PRIORITY);
    System.out.println("Thread Coureur " + hinaut.nom 
              + " a la priorite = " + hinaut.getPriority());
    System.out.println("Thread Coureur " + poulidor.nom 
              + " a la priorite = " + poulidor.getPriority());
    hinaut.start();
    poulidor.start();
  }
}
class Coureur extends Thread {

  String nom;
  public Coureur(String nom) {
    super(nom);
    this.nom = nom;
  }
  public void run() {
    long coupsDePedale  = 0;
    while (coupsDePedale < 5000000L) {
      coupsDePedale++;
      if ((coupsDePedale % 500000L) == 0) {
        System.out.println("Coureur " + nom 
        + " a donne " + coupsDePedale + " coups de pedale.");
      }
    }
  }
}
EXECUTION
Thread Coureur Hinaut a la priorite = 10
Thread Coureur Poulidor a la priorite = 1
Coureur Hinaut a donne 500000 coups de pedale.
Coureur Hinaut a donne 1000000 coups de pedale.
Coureur Hinaut a donne 1500000 coups de pedale.
Coureur Hinaut a donne 2000000 coups de pedale.
Coureur Hinaut a donne 2500000 coups de pedale.
Coureur Hinaut a donne 3000000 coups de pedale.
Coureur Hinaut a donne 3500000 coups de pedale.
Coureur Hinaut a donne 4000000 coups de pedale.
Coureur Hinaut a donne 4500000 coups de pedale.
Coureur Hinaut a donne 5000000 coups de pedale.
Coureur Poulidor a donne 500000 coups de pedale.
Coureur Poulidor a donne 1000000 coups de pedale.
Coureur Poulidor a donne 1500000 coups de pedale.
Coureur Poulidor a donne 2000000 coups de pedale.
Coureur Poulidor a donne 2500000 coups de pedale.
Coureur Poulidor a donne 3000000 coups de pedale.
Coureur Poulidor a donne 3500000 coups de pedale.
Coureur Poulidor a donne 4000000 coups de pedale.
Coureur Poulidor a donne 4500000 coups de pedale.
Coureur Poulidor a donne 5000000 coups de pedale.
  • les lignes de la classe :
    • la méthode setPriority fixe le niveau de priorité entre les différents threads
      • la valeur doit être comprise entre une valeur minimale, variable de classe MIN_PRIORITY, et maximale, MAX_PRIORITY
  • le temps alouée fut visiblement plus court que dans la version 1 de la course infernale !

yield ...

Source de BavarderEtLancerLePerroquet8.java
class BavarderEtLancerLePerroquet8
{ 
  public static void main(String args[]) {
    Perroquet8 perroquet = new Perroquet8("coco",10);
    perroquet.start();
    for (int n=0; n<10; n++) {
      blabla();   
      Thread.currentThread().yield();
    }
  }
  private static void blabla() {
    System.out.println("blabla");
 }
}

class Perroquet8 extends Thread
{
  private String cri = null;
  private int fois = 0;
  public Perroquet8(String s, int i)   { 
    cri = s;
    fois = i;
  }
  public void repeter() {
    System.out.println(cri);
  }
  public void run() {
    for (int n=0; n<fois; n++) {
      repeter();
      yield();
    }
  }
}
EXECUTION
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
  • les lignes de la classe :
    • la méthode yield() "rend le processeur" : elle indique au contrôlleur d'exécution des threads d'en choisir un nouveau à exécuter (donc ca pourrait être le même !)
      • elle est interressante dans peu de cas

Essayer de Stopper l'exécution d'un thread

Source de LancerEtArreterLePerroquet9.java
class LancerEtArreterLePerroquet9
{ 
  public static void main(String args[]) {
    Perroquet9 perroquet = new Perroquet9("coco");
    perroquet.start();
    String reponse="oui";
    do {
      System.out.println("voulez-vous que le perroquet continue ? (o/n)");
      Thread.currentThread().yield();
      reponse = Clavier.lireString();
    }
    while (reponse.equals("o"));
  }
}

class Perroquet9 extends Thread
{
  private String cri = null;
  public Perroquet9(String s)   { 
    cri = s;
  }
  public void repeter() {
    System.out.println(cri);
    try { 
      Thread.sleep((int)Math.random()*1000); 
    }
    catch(InterruptedException e) {}
  }
  public void run() {
    while (true) {
      repeter();
      yield();
    }
  }
}
EXECUTION
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
...
  • Le probléme est que l'arret de l'exécution du thread main n'entraine pas l'arret du thread perroquet
  • la Classe Thread a une méthode stop() "deprecated" : l'arret était brutal et pouvait laisser des objets dans des états inconsistants ! par exemple, des verrous pouvaient avoir été posés. C'est au programmeur de prévoir quand (et donc comment) l'exécution du thread peut s'arreter sans risque.
    • idem pour suspend( ) et resume( ) qui suspendait et reprenait l'exécution d'un thread

Interrompre un thread

Source de LancerEtArreterLePerroquet13.java
class LancerEtArreterLePerroquet13
{ 
  public static void main(String args[]) {
    Perroquet13 perroquet = new Perroquet13("coco");
    perroquet.start();
    String reponse="n";
    do {
      System.out.println("voulez-vous interrompre le perroquet ?(o/n)");
      Thread.currentThread().yield();
      reponse = Clavier.lireString();
    }
    while (! reponse.equals("o"));
    perroquet.interrupt();
  }
}

class Perroquet13 extends Thread
{
  private String cri = null;
  public Perroquet13(String s)   { 
    cri = s;
  }
  public void repeter() throws InterruptedException {
    System.out.println(cri);
    Thread.sleep((int)(Math.random()*3000)); 
  }
  public void run() {
    try { 
      while (true) {
        repeter();
        yield();
      }
    }
    catch(InterruptedException e) {
      System.out.println("perroquet interrompu :\n"
                         + e.getMessage());
    }
  }
}
EXECUTION
voulez-vous interrompre le perroquet ?(o/n)
coco
coco
coco
coco
n
voulez-vous interrompre le perroquet ?(o/n)
coco
coco
o
perroquet interrompu :
sleep interrupted
  • les lignes de la classe :
    • il est possible d'interrompre un thread
    • le thread interrompu termine immédiatement son exécution de la méthode run()
    • une exception est levée puis capturée
  • la méthode interrupt() de Thread interrompt le thread et lève une exception InterruptedException
  • la méthode sleep() est susceptible de générer une exception InterruptedException puisque que c'est la seule façon de "sortir avant terme".
  • c'est une technique violente pour terminer un thread : il faut au moins faire un try-catch de l'exception InterruptedException sur tout le bloc de run() pour essayer de terminer proprement (annuler les locks, ...)

Stopper proprement

Source de LancerEtArreterLePerroquet10.java
class LancerEtArreterLePerroquet10
{ 
  public static void main(String args[]) {
    Perroquet10 perroquet = new Perroquet10("coco");
    perroquet.start();
    String reponse="o";
    do {
      System.out.println("voulez-vous que le perroquet continue ? (o/n)");
      Thread.currentThread().yield();
      reponse = Clavier.lireString();
    }
    while (reponse.equals("o"));
    perroquet.stopper();
  }
}

class Perroquet10 extends Thread
{
  private volatile boolean continuer = true;
  private String cri = null;
  public Perroquet10(String s)   { 
    continuer = true;
    cri = s;
  }
  public void repeter() {
    System.out.println(cri);
    try { 
      Thread.sleep((int)Math.random()*2000); 
    }
    catch(InterruptedException e) { }
  }  
  public void stopper() {
    continuer = false;
  }
  
  public void run() {
    while (continuer) {
      repeter();
      yield();
    }
  }
}
EXECUTION
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco
ncoco
coco
coco
coco
coco
coco
  • les lignes de la classe :
    • La solution est très simple : un drapeau booléen sert dans la méthode run pour savoir s'il faut continuer ou arreter.
      • cette variable doit être déclarée volatile
      • afin d'empêcher toute optimisation du code qui entrainerait la création de double temporaire de cette variable

Timer

Source de DeclancherLePerroquet11.java
import java.util.TimerTask;
import java.util.Timer;
class DeclancherLePerroquet11
{ 
  public static void main(String args[]) {
    Perroquet11 perroquet = new Perroquet11("coco", 3);
    Timer timer = new Timer();
    timer.schedule(perroquet, 4000);
    String reponse="oui";
    do {
      System.out.println("blabla");
      System.out.println("blabla");     
      System.out.println("voulez-vous encore bavarder ? (o/n)");
      reponse = Clavier.lireString();
    }
    while (reponse.equals("o"));
    timer.cancel();
  }
}

class Perroquet11 extends TimerTask
{
  private String cri = null;
  private int fois = 0;
  public Perroquet11(String s, int i)   { 
    cri = s;
    fois = i;
  }
  public void repeter() {
    System.out.println(cri);
  }
  public void run() {
    for (int i = 0; i < fois; i++)  {
      repeter();
    }
  }
}
EXECUTION
blabla
blabla
voulez-vous encore bavarder ? (o/n)
o
blabla
blabla
voulez-vous encore bavarder ? (o/n)
coco
coco
coco
o
blabla
blabla
voulez-vous encore bavarder ? (o/n)
n
  • les lignes de la classe :
    • Un Timer permet de déclancher l'exécution de taches une ou plusieurs fois en précisant un délai initial et/ou une périodicité.
      • plusieurs taches peuvent etre programmées selon des programmes divers.
      • A un Timer correspond un thread qui exécutera successivement les taches à effectuer.
      • Les taches sont des TimerTask et doivent être courte
      • Aucune garantie de temps réel n'est assurée par ce mécanisme
      • la méthode schedule(...) permet de programmer les tachesla méthode
        • schedule(tache, long millisecondes) programme la tache toutes les millisecondes
      • la méthode cancel() arrete toute la programmation du Timer, mais si une tache est en cours, elle continue son execution
    • la classe abstraite TimerTask implémente Runnable, donc une méthode run()
      • il faut donc hériter de la classe TimerTask et redéfinir la méthode run() qui code la tache à effectuer.

Source de DeclancherLePerroquet12.java
...
timer.schedule(perroquet, 2000, 1000);
...

Exercices

Source de Ecrivain1.java
import java.util.Vector;
import java.util.StringTokenizer;
public class Ecrivain1 extends Thread  {
  private Vector texte;
  public Ecrivain1(String t) {
    super();
    texte = new Vector();
    StringTokenizer st = new StringTokenizer(t); 
  	while (st.hasMoreTokens())
	    texte.addElement(st.nextToken());
  }
  public void run() {
    for (int i=0; i<10; i++) {  
      int j=0;
      for (;j<texte.size()-1; j++) {
        System.out.print(texte.elementAt(j)+" "); 
        try { 
          sleep((long)(Math.random() * 100)); 
        } catch (InterruptedException e) {} 
      } 
      System.out.println(texte.elementAt(j)); 
    } 
    System.out.println("ecrivain de "+texte+" a fini");
  }
}

  1. les lignes de la classe :
  2. 2 écrivains indépendants
    Ecrire un programme correction qui crée 2 écrivains :
  3. Terminaison :
    Le programme "main" attendra que les 2 écrivains aient finis leurs écritures pour afficher un "bonsoir" et arreter.
    correction
  4. Accélerer
    "
    Doublez" (si possible) la vitesse d'écriture d'un des 2 écrivains par rapport à l'autre correction
  5. Stopper
    Donnez la possibilité au thread main d'arrêter un ou les 2 écrivains
    correction     correction
  6. Programmation temporelle
    Un seul écrivain écrira son texte plusieurs fois ainsi : il l'écrira 10 fois à 2 secondes d'intervalles; 4 secondes s'écouleront avant la 1ère écriture; il "prend" environ 50 millisecondes pour écrire chaque lettre.
    correction