IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 14 - Les Threads multiples

pages : 1 2 3 4 5 6 7 8 9 


(1) ThreadGroup[name=system,maxpri=10]
      Thread[main,5,system]
(2) ThreadGroup[name=system,maxpri=9]
      Thread[main,6,system]
(3) ThreadGroup[name=g1,maxpri=9]
      Thread[A,9,g1]
(4) ThreadGroup[name=g1,maxpri=8]
      Thread[A,9,g1]
(5) ThreadGroup[name=g1,maxpri=8]
      Thread[A,9,g1]
      Thread[B,8,g1]
(6) ThreadGroup[name=g1,maxpri=3]
      Thread[A,9,g1]
      Thread[B,8,g1]
      Thread[C,6,g1]
(7) ThreadGroup[name=g1,maxpri=3]
      Thread[A,9,g1]
      Thread[B,8,g1]
      Thread[C,3,g1]
(8) ThreadGroup[name=g2,maxpri=3]
(9) ThreadGroup[name=g2,maxpri=3]
(10)ThreadGroup[name=system,maxpri=9]
      Thread[main,6,system]
      ThreadGroup[name=g1,maxpri=3]
        Thread[A,9,g1]
        Thread[B,8,g1]
        Thread[C,3,g1]
        ThreadGroup[name=g2,maxpri=3]
          Thread[0,6,g2]
          Thread[1,6,g2]
          Thread[2,6,g2]
          Thread[3,6,g2]
          Thread[4,6,g2]
Starting all threads:
All threads started

Tous les programmes ont au moins un thread qui tourne, et la première action de main() est d'appeler la méthode static de Thread nommée currentThread(). Depuis ce thread, le groupe de thread est produit et list() est appelé sur le résultat. La sortie est:


(1) ThreadGroup[name=system,maxpri=10]
      Thread[main,5,system]

Vous pouvez voir que le nom du groupe de thread principal est system, et le nom du thread principal est main, et il appartient au groupe de thread system.

Le second exercice montre que la priorité maximum du groupe system peut être réduite et le thread main peut avoir sa priorité augmentée.


(2) ThreadGroup[name=system,maxpri=9]
      Thread[main,6,system]

Le troisième exercice crée un nouveau groupe de thread, g1, qui appartient automatiquement au groupe de thread system puisqu'il n'est rien spécifié d'autre. Un nouveau thread A est placé dans g1. Après avoir essayer de positionner la priorité maximum de ce groupe au plus haut niveau possible et la priorité de A au niveau le plus élevé, le résultat est:


(3) ThreadGroup[name=g1,maxpri=9]
      Thread[A,9,g1]

Ainsi, il n'est pas possible de changer la priorité maximum d'un groupe de thread au delà de celle de son groupe de thread parent.

Le quatrième exercice réduit la priorité maximum de g1 de deux et essaie ensuite de l'augmenter jusqu'à Thread.MAX_PRIORITY. Le résultat est:


(4) ThreadGroup[name=g1,maxpri=8]
      Thread[A,9,g1]

Vous pouvez voir que l'augmentation à la priorité maximum ne fonctionne pas. Vous pouvez seulement diminuer la priorité maximum d'un groupe de thread, pas l'augmenter. Notez également que la priorité du thread A n'a pas changé, et est maintenant plus grande que la priorité maximum du groupe de thread. Changer la priorité maximum d'un groupe de thread n'affecte pas les threads existantes.

Le cinquième exercice essaie de créer un nouveau thread avec une priorité au maximum:


(5) ThreadGroup[name=g1,maxpri=8]
      Thread[A,9,g1]
      Thread[B,8,g1]

Le nouveau thread ne peut pas être changer à une priorité plus haute que la priorité maximum du groupe de thread.

La priorité par défaut du thread pour ce programme est six; c'est la priorité avec laquelle un nouveau thread sera créé et à laquelle il restera si vous ne manipulez pas la priorité. L'exercice 6 diminue la priorité maximum du groupe de thread en dessous de la priorité par défaut pour voir ce qui se passe quand vous créez un nouveau thread dans ces conditions:


(6) ThreadGroup[name=g1,maxpri=3]
      Thread[A,9,g1]
      Thread[B,8,g1]
      Thread[C,6,g1]

Étant donné que la priorité maximum du groupe de thread est trois, le nouveau thread est encore créer en utilisant la priorité par défaut de six. Ainsi, la priorité maximum du groupe de thread n'affecte pas la priorité par défaut. (En fait, il ne semble pas y avoir de moyen pour positionner la priorité par défaut des nouveaux threads.)

Après avoir changer la priorité, en essayant de la décrémenter par pas de un, le résultat est:


(7) ThreadGroup[name=g1,maxpri=3]
      Thread[A,9,g1]
      Thread[B,8,g1]
      Thread[C,3,g1]

La priorité maximum du groupe de thread est forcé seulement lorsque vous essayez de changer la priorité du thread.

Une expérience similaire est effectué en (8) et (9), dans lequel un nouveau groupe de thread g2 est créé comme un fils de g1 et sa priorité maximum est changée. Vous pouvez voir qu'il n'est pas impossible pour la priorité maximum de g2 de devenir plus grande que celle de g1:


(8) ThreadGroup[name=g2,maxpri=3]
(9) ThreadGroup[name=g2,maxpri=3]

Notez également que g2 est automatiquement mise à la priorité maximum du groupe de thread g1 dès la création de g2.

Après toutes ces expériences, le système de groupes de threads est entièrement donné:


(10)ThreadGroup[name=system,maxpri=9]
      Thread[main,6,system]
      ThreadGroup[name=g1,maxpri=3]
        Thread[A,9,g1]
        Thread[B,8,g1]
        Thread[C,3,g1]
        ThreadGroup[name=g2,maxpri=3]
          Thread[0,6,g2]
          Thread[1,6,g2]
          Thread[2,6,g2]
          Thread[3,6,g2]
          Thread[4,6,g2]

Donc à cause des règles sur les groupes de threads, un groupe fils doit toujours avoir une priorité maximum plus petite ou égale à celle de son parent.

La dernière partie de ce programme démontre les méthodes pour un groupe de threads entier. Dans un premier temps le programme parcourt l'intégralité de l'arbre de threads et démarre chaque thread qui ne l'est pas déjà. Par drame, le groupe system est alors suspendu et finalement arrêter. (Bien qu'il soit intéressant de voir que suspend() et stop() fonctionnent sur un groupe de thread entier, vous devez garder en tête que ces méthodes sont dépréciées en Java 2.) Mais quand vous suspendez le groupe system vous suspendez aussi le thread main et le programme entier s'arrête, donc il ne quittera jamais le point où les threads sont stoppés. Actuellement, si vous stoppez le thread main il déclenche un exception ThreadDeath, ce n'est donc pas une chose à faire. Puisque ThreadGroup hérite de Object, qui contient la méthode wait(), vous pouvez aussi choisir de suspendre le programme pour un certain nombre de secondes en appelant wait(secondes * 1000). Ce qui doit acquérir le verrou dans un bloc synchronised, bien sûr.

La classe ThreadGroup a aussi des méthodes suspend() et resume() donc vous pouvez arrêter et démarrer un groupe de thread entier et toutes ces threads et sous-groupes avec une seule commande. (Encore un fois, suspend() et resume() sont déprécié en Java 2.)

Les groupes de threads peuvent paraître un peu mystérieux au premier abord, mais garder en mémoire que vous ne les utiliserez probablement directement très peu souvent.

Runnable revisité

Précédemment dans ce chapitre, j'ai suggéré que vous deviez faire très attention avant de faire d'une applet ou une Frame principale une implémentation de Runnable. Bien sûr, si vous devez héritez d'une classe et que vous voulez ajouter un comportement multitâche à la classe, Runnable est la solution correcte. L'exemple final de ce chapitre exploite ceci en créant une classe Runnable JPanel qui se peint de différentes couleurs. Cette application est prend des valeurs depuis la ligne de commande pour déterminer la taille de la grille de couleurs et la durée du sleep() entre les changements de couleurs. En jouant sur ces valeurs vous découvrirez des possibilités intéressantes et parfois inexplicable des threads:


//: c14:ColorBoxes.java
// Utilisation de l'interface Runnable.
// <applet code=ColorBoxes width=500 height=400>
// <param name=grid value="12">
// <param name=pause value="50">
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

class CBox extends JPanel implements Runnable {
  private Thread t;
  private int pause;
  private static final Color[] colors = {
    Color.black, Color.blue, Color.cyan,
    Color.darkGray, Color.gray, Color.green,
    Color.lightGray, Color.magenta,
    Color.orange, Color.pink, Color.red,
    Color.white, Color.yellow
  };
  private Color cColor = newColor();
  private static final Color newColor() {
    return colors[
      (int)(Math.random() * colors.length)
    ];
  }
  public void paintComponent(Graphics  g) {
    super.paintComponent(g);
    g.setColor(cColor);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
  }
  public CBox(int pause) {
    this.pause = pause;
    t = new Thread(this);
    t.start();
  }
  public void run() {
    while(true) {
      cColor = newColor();
      repaint();
      try {
        t.sleep(pause);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
    }
  }
}

public class ColorBoxes extends JApplet {
  private boolean isApplet = true;
  private int grid = 12;
  private int pause = 50;
  public void init() {
    // Récupère les paramètres depuis la page web:
    if (isApplet) {
      String gsize = getParameter("grid");
      if(gsize != null)
        grid = Integer.parseInt(gsize);
      String pse = getParameter("pause");
      if(pse != null)
        pause = Integer.parseInt(pse);
    }
    Container cp = getContentPane();
    cp.setLayout(new GridLayout(grid, grid));
    for (int i = 0; i < grid * grid; i++)
      cp.add(new CBox(pause));
  }
  public static void main(String[] args) {
    ColorBoxes applet = new ColorBoxes();
    applet.isApplet = false;
    if(args.length > 0)
      applet.grid = Integer.parseInt(args[0]);
    if(args.length > 1)
      applet.pause = Integer.parseInt(args[1]);
    Console.run(applet, 500, 400);
  }
} ///:~

ColorBoxes est l'applet/application habituelle avec une méthode init() qui créé la GUI. Elle positionne la GridLayout afin d'avoir une grille de cellules dans chaque dimension. Ensuite elle ajoute le nombre approprié d'objet CBox pour remplir la grille, passant la valeur pause a chacune. Dans main() vous pouvez voir comment pause et grid ont des valeurs par défaut qui peuvent être changées si vous passez des arguments à la ligne de commande, ou en utilisant des arguments de l'applet.

CBox est là où tout le travail s'effectue. Elle hérite de JPanel et implémente l'interface Runnable ainsi chaque JPanel peut aussi être un Thread. Souvenez vous que quand vous implémentez Runnable, vous ne faites pas un objet Thread, mais juste une classe qui a une méthode run(). Ainsi, vous devez créer explicitement un objet Thread et passer l'objet Runnable au constructeur, puis appelez start() (ce qui est fait dans le constructeur). Dans CBox ce thread est appelé t.

Remarquez le tableau colors, qui est une énumération de toutes les couleurs de la classe Color. Il est utilisé dans newColor() pour produire une couleur sélectionnée au hasard. La couleur de la cellule courante est cColor.

paintComponent() est assez simple — elle place juste la couleur à cColor et remplit intégralement le JPanel avec cette couleur.

Dans run(), vous voyez la boucle infinie qui place la cColor à une nouvelle couleur prise au hasard et ensuite appelle repaint() pour la montrer. Puis le thread passe dans sleep() pour le temps spécifié sur la ligne de commande.

C'est précisément parce que le design est flexible et que le threading est attaché à chaque élément JPanel, que vous pouvez expérimenter en créant autant de threads que vous voulez. (En réalité, il y a une restriction imposé par le nombre de threads que votre JVM peut confortablement gérés.)

Ce programme fait aussi un benchmark intéressant, puisqu'il peut montrer des différences de performance dramatiques entre deux implémentation du threading dans les JVM.

Trop de threads

A un certain point, vous trouverez que ColorBoxes ralentit. Sur ma machine, cela se passe quelque part après une grille 10 x 10. Pourquoi est ce que cela arrive? Vous soupçonner naturellement que Swing ai quelques chose à voir avec ça, donc voici un exemple qui test ce prémisse en faisant moins de threads. Le code suivant est réorganisé afin qu'une ArrayList implements Runnable et cette ArrayList gère un nombre de blocs de couleurs et en choisit une au hasard pour la mise à jour. Puis un certain nombre de ces objets ArrayList sont créés, en fonction d'une approximation de la dimension de la grille que vous choisissez. Au résultat, vous avez beaucoup moins de threads que de blocs de couleurs, donc si il y a une accélération nous saurons que c'était à cause du trop grand nombre de threads dans l'exemple précédent:


//: c14:ColorBoxes2.java
// Compromis dans l'utilisation de thread.
// <applet code=ColorBoxes2 width=600 height=500>
// <param name=grid value="12">
// <param name=pause value="50">
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;

class CBox2 extends JPanel {
  private static final Color[] colors = {
    Color.black, Color.blue, Color.cyan,
    Color.darkGray, Color.gray, Color.green,
    Color.lightGray, Color.magenta,
    Color.orange, Color.pink, Color.red,
    Color.white, Color.yellow
  };
  private Color cColor = newColor();
  private static final Color newColor() {
    return colors[
      (int)(Math.random() * colors.length)
    ];
  }
  void nextColor() {
    cColor = newColor();
    repaint();
  }
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor(cColor);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
  }
}

class CBoxList
  extends ArrayList implements Runnable {
  private Thread t;
  private int pause;
  public CBoxList(int pause) {
    this.pause = pause;
    t = new Thread(this);
  }
  public void go() { t.start(); }
  public void run() {
    while(true) {
      int i = (int)(Math.random() * size());
      ((CBox2)get(i)).nextColor();
      try {
        t.sleep(pause);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
    }
  }
  public Object last() { return get(size() - 1);}
}

public class ColorBoxes2 extends JApplet {
  private boolean isApplet = true;
  private int grid = 12;
  // Pause par défaut plus courte que dans ColorBoxes:
  private int pause = 50;
  private CBoxList[] v;
  public void init() {
    // Récupère les paramètres de la page Web:
    if (isApplet) {
      String gsize = getParameter("grid");
      if(gsize != null)
        grid = Integer.parseInt(gsize);
      String pse = getParameter("pause");
      if(pse != null)
        pause = Integer.parseInt(pse);
    }
    Container cp = getContentPane();
    cp.setLayout(new GridLayout(grid, grid));
    v = new CBoxList[grid];
    for(int i = 0; i < grid; i++)
      v[i] = new CBoxList(pause);
    for (int i = 0; i < grid * grid; i++) {
      v[i % grid].add(new CBox2());
      cp.add((CBox2)v[i % grid].last());
    }
    for(int i = 0; i < grid; i++)
      v[i].go();
  }  
  public static void main(String[] args) {
    ColorBoxes2 applet = new ColorBoxes2();
    applet.isApplet = false;
    if(args.length > 0)
      applet.grid = Integer.parseInt(args[0]);
    if(args.length > 1)
      applet.pause = Integer.parseInt(args[1]);
    Console.run(applet, 500, 400);
  }
} ///:~

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par Cédric Babault ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 4 5 6 7 8 9 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java