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 

La priorité d'un thread dit à l'ordonnanceur [scheduler] l'importance de ce thread. Si il y a un certain nombre de threads bloqués et en attente d'exécution, l'ordonnanceur exécutera celui avec la plus haute priorité en premier. Cependant, cela ne signifie pas que les threads avec des priorités plus faibles ne tourneront pas (en fait, vous ne pouvez pas avoir d'interblocage à cause des priorités). Les threads de priorités plus faibles ont juste tendance à tourner moins souvent.

Bien qu'il soit intéressant de connaître et de jouer avec les priorités, en pratique vous n'avez pratiquement jamais besoin de gérer les priorités par vous même. Donc soyez libre de passer le reste de cette session si les priorités ne vous intéressent pas.

Lire et changer les priorités

Vous pouvez lire la priorité d'un thread avec getPriority() et la changer avec setPriority(). La forme des précédents exemples « counter » peut être utiliser pour montrer l'effet des changements de priorités. Dans cette applet vous verrez que les compteurs ralentissent quand les threads associés ont leurs priorités diminuées:


//: c14:Counter5.java
// Ajuster les priorités des threads.
// <applet code=Counter5 width=450 height=600>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

class Ticker2 extends Thread {
  private JButton
    b = new JButton("Toggle"),
    incPriority = new JButton("up"),
    decPriority = new JButton("down");
  private JTextField
    t = new JTextField(10),
    pr = new JTextField(3); // Affiche la priorité
  private int count = 0;
  private boolean runFlag = true;
  public Ticker2(Container c) {
    b.addActionListener(new ToggleL());
    incPriority.addActionListener(new UpL());
    decPriority.addActionListener(new DownL());
    JPanel p = new JPanel();
    p.add(t);
    p.add(pr);
    p.add(b);
    p.add(incPriority);
    p.add(decPriority);
    c.add(p);
  }
  class ToggleL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      runFlag = !runFlag;
    }
  }
  class UpL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      int newPriority = getPriority() + 1;
      if(newPriority > Thread.MAX_PRIORITY)
        newPriority = Thread.MAX_PRIORITY;
      setPriority(newPriority);
    }
  }
  class DownL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      int newPriority = getPriority() - 1;
      if(newPriority < Thread.MIN_PRIORITY)
        newPriority = Thread.MIN_PRIORITY;
      setPriority(newPriority);
    }
  }
  public void run() {
    while (true) {
      if(runFlag) {
        t.setText(Integer.toString(count++));
        pr.setText(
          Integer.toString(getPriority()));
      }
      yield();
    }
  }
}

public class Counter5 extends JApplet {
  private JButton
    start = new JButton("Start"),
    upMax = new JButton("#004488">"Inc Max Priority"),
    downMax = new JButton("#004488">"Dec Max Priority");
  private boolean started = false;
  private static final int SIZE = 10;
  private Ticker2[] s = new Ticker2[SIZE];
  private JTextField mp = new JTextField(3);
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    for(int i = 0; i < s.length; i++)
      s[i] = new Ticker2(cp);
    cp.add(new JLabel(
      "MAX_PRIORITY = " + Thread.MAX_PRIORITY));
    cp.add(new JLabel("MIN_PRIORITY = "
      + Thread.MIN_PRIORITY));
    cp.add(new JLabel("#004488">"Group Max Priority = "));
    cp.add(mp);
    cp.add(start);
    cp.add(upMax);
    cp.add(downMax);
    start.addActionListener(new StartL());
    upMax.addActionListener(new UpMaxL());
    downMax.addActionListener(new DownMaxL());
    showMaxPriority();
    // Affiche récursivement les groupes de thread parents:
    ThreadGroup parent =
      s[0].getThreadGroup().getParent();
    while(parent != null) {
      cp.add(new Label(
        "Parent threadgroup max priority = "
        + parent.getMaxPriority()));
      parent = parent.getParent();
    }
  }
  public void showMaxPriority() {
    mp.setText(Integer.toString(
      s[0].getThreadGroup().getMaxPriority()));
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(!started) {
        started = true;
        for(int i = 0; i < s.length; i++)
          s[i].start();
      }
    }
  }
  class UpMaxL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      int maxp =
        s[0].getThreadGroup().getMaxPriority();
      if(++maxp > Thread.MAX_PRIORITY)
        maxp = Thread.MAX_PRIORITY;
      s[0].getThreadGroup().setMaxPriority(maxp);
      showMaxPriority();
    }
  }
  class DownMaxL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      int maxp =
        s[0].getThreadGroup().getMaxPriority();
      if(--maxp < Thread.MIN_PRIORITY)
        maxp = Thread.MIN_PRIORITY;
      s[0].getThreadGroup().setMaxPriority(maxp);
      showMaxPriority();
    }
  }
  public static void main(String[] args) {
    Console.run(new Counter5(), 450, 600);
  }
} ///:~

Ticker2 suit la forme établie plus tôt dans ce chapitre, mais il y a un JTextField supplémentaire pour afficher la priorité du thread et deux bouttons de plus pour incrémenter et décrémenter la priorité.

Vous pouvez également noter l'utilisation de yield(), qui rend volontairement la main à l'ordonnanceur. Sans cela le mécanisme de multithreading fonctionne encore, mais vous remarquerez qu'il tourne plus lentement (essayez de supprimer l'appel à yield() pour le voir). Vous pouvez aussi appeler sleep(), mais alors la vitesse de comptage sera controller par la durée du sleep() au lieu de la priorité.

Le init() dans Counter5 crée un tableau de dix Ticker2s; leurs bouttons et champs sont placés sur le formulaire par le constructeur de Ticker2. Counter5 ajoute des bouttons pour tout démarrer aussi bien que pour incrémenter et décrémenter la priorité maximum du groupe de thread. En plus, il y a des labels qui affichent les priorités maximum et minimum possibles pour un thread et un JTextField pour montrer la priorité maximum du groupe de thread. (La prochaine section décrira les groupes de threads.) Finalement, les priorités des groupes de threads parents sont aussi afficher comme labels.

Quand vous pressez un bouton « up » ou « down », la priorité du Tricker2 est rapportée et incrémenter ou décrémenter en conséquence.

Quand vous exécutez ce programme, vous noterez plusieurs choses. Tout d'abord, la priorité du groupe de thread est par défaut cinq. Même si vous décrémentez la priorité maximum en dessous de cinq avant de démarrer les threads (ou avant de les créer, ce qui nécessite un changement du code), chaque thread aura une priorité par défaut de cinq.

Le test simple est de prendre un compteur et de décrémenter sa priorité jusqu'à un, et observez qu'il compte beaucoup plus lentement. Mais maintenant essayez de l'incrémenter de nouveau. Vous pouvez le ramené à la priorité du groupe de thread, mais pas plus haut. Maintenant décrémentez la priorité du groupe de threads. Les priorités des threads restent inchangées, mais si vous essayez de les modifier dans un sens ou dans l'autre vous verrez qu'elles sauteront automatiquement à la priorité du groupe de thread. Les nouvelles threads auront également une priorité par défaut qui peut être plus haute que la priorité du groupe. (Ainsi la priorité du groupe n'est pas un moyen d'empêcher les nouveaux threads d'avoir des priorités plus hautes que celles existantes.)

Finalement, essayez d'incrémenter la priorité maximum du groupe. On ne peut pas le faire. Vous pouvez seulement réduire la priorité maximum d'un groupe de thread, pas l'augmenter.

Les groupes de threads

Tous les threads appartiennent à un groupe de thread. Ce peut être soit le groupe de thread par défaut, soit un groupe de thread que vous spécifiez explicitement quand vous créez le thread. A la création, le thread est attaché à un groupe et ne peut pas en changer. Chaque application a au moins un thread qui appartient au groupe de thread système. Si vous créez plus de threads sans spécifier de groupe, ils appartiendront aussi au groupe de thread système.

Les groupes de threads doivent aussi appartenir à d'autres groupes de threads. Le groupe de thread auquel un nouveau appartient doit être spécifié dans le constructeur. Si vous créez un groupe de thread sans spécifier de groupe auquel le rattacher, il sera placer dans le groupe de thread système. Ainsi, tous les groupes de threads de votre application auront en fin de compte le groupe de thread système comme parent.

La raison de l'existence des groupes de threads est difficile à déterminer à partir de la littérature, qui tend à être confuse sur le sujet. Il est souvent cité des raisons de sécurités. D'après Arnold & Gosling,« Les threads d'un groupe peuvent modifier les autres threads du groupe, y compris ceux situés plus bas dans la hiérarchie. Un thread ne peut pas modifier les threads extérieurs à son propre groupe ou les groupes qu'il contient. » Il est difficile de savoir ce que « modifier » est supposé signifier ici. L'exemple suivant montre un thread dans un sous-groupe « feuille » modifier les priorités de tous les threads de son arbre de groupe de thread aussi bien qu'appeler une méthode pour tous les threads de son arbre.


//: c14:TestAccess.java
// Comment les threads peuvent accéder aux
// autres threads dans un groupe de threads parent.

public class TestAccess {
  public static void main(String[] args) {
    ThreadGroup
      x = new ThreadGroup("x"),
      y = new ThreadGroup(x, "y"),
      z = new ThreadGroup(y, "z");
    Thread
      one = new TestThread1(x, "one"),
      two = new TestThread2(z, "two");
  }
}

class TestThread1 extends Thread {
  private int i;
  TestThread1(ThreadGroup g, String name) {
    super(g, name);
  }
  void f() {
    i++; // modifie ce thread
    System.out.println(getName() + " f()");
  }
}

class TestThread2 extends TestThread1 {
  TestThread2(ThreadGroup g, String name) {
    super(g, name);
    start();
  }
  public void run() {
    ThreadGroup g =
      getThreadGroup().getParent().getParent();
    g.list();
    Thread[] gAll = new Thread[g.activeCount()];
    g.enumerate(gAll);
    for(int i = 0; i < gAll.length; i++) {
      gAll[i].setPriority(Thread.MIN_PRIORITY);
      ((TestThread1)gAll[i]).f();
    }
    g.list();
  }
} ///:~

Dans main(), plusieurs ThreadGroups sont créés, placés en feuilles des autres: x n'a pas d'autres arguments que son nom (une String), ainsi il est automatiquement placé dans le groupe de threads « système », tandis que y est sous x et z est sous y. Notez que l'initialisation se passe dans l'ordre textuel donc ce code est légal.

Deux threads sont créés et placés dans différents groupes de threads. TestThread1 n'a pas de méthode run() mais a une méthode f() qui modifie le thread et écrit quelque chose afin que vous puissiez voir qu'elle a été appelée. TestThread2 est une sous classe de TestThread1 et sa méthode run() est assez élaboré. Elle récupère d'abord le groupe de thread du thread courant, puis remonte deux niveaux dans l'arbre d'héritage en utilisant getParent(). (Ceci est étudié puisque j'ai intentionnellement placer l'objet TestThread2 deux niveaux plus bas dans la hiérarchie.) A ce point, un tableau de références sur Thread est créé en utilisant la méthode activeCount() pour demander combien de threads sont dans ce groupe de thread et tous les groupes fils. La méthode enumerate() place les références à toutes ces threads dans le tableau gAll, ensuite je me parcourt simplement dans la totalité du tableau en appelant la méthode f() pour chaque thread, ainsi qu'en modifiant la priorité. Ainsi, un thread dans un groupe de thread « feuille » modifie les threads dans les groupes de threads parents.

La méthode de déboguage list() affiche toutes l'information sur un groupe de thread sur la sortie standard ce qui aide beaucoup lorsqu'on examine le comportement d'un groupe de threads. Voici la sortie du programme:


java.lang.ThreadGroup[name=x,maxpri=10]
    Thread[one,5,x]
    java.lang.ThreadGroup[name=y,maxpri=10]
        java.lang.ThreadGroup[name=z,maxpri=10]
            Thread[two,5,z]
one f()
two f()
java.lang.ThreadGroup[name=x,maxpri=10]
    Thread[one,1,x]
    java.lang.ThreadGroup[name=y,maxpri=10]
        java.lang.ThreadGroup[name=z,maxpri=10]
            Thread[two,1,z]

Non seulement list() affiche le nom du ThreadGroup ou du Thread, mais elle affiche aussi le nom du groupe de thread et sa priorité maximum. Pour les threads, le nom de thread est également affiché, suivi par la priorité du thread et le groupe auquel il appartient. Notez que list() indente les threads et groupes de threads pour indiquer qu'ils sont enfant du groupe de threads non indenté.

Vous pouvez voir que f() est appelé par la méthode run() de TestThread2, il est donc évident que tous les threads d'un groupe sont vulnérables. Cependant, vous ne pouvez accéder seulement aux threads embranchés sur votre propre arbre du groupe de thread système, et peut-être que c'est ce que signifie « sûr. » Vous ne pouvez pas accéder à l'arbre du groupe de thread système d'un autre.

Controller les groupes de threads

En dehors de l'aspect sécurité, une chose pour lesquelles les groupes de threads semble être utiles est le controle: vous pouvez effectuer certaines opérations sur un groupe de thread entier avec une seule commande. L'exemple suivant le démontre, et les restrictions sur les priorités avec les groupes de threads. Les numéros entre parenthèses donne une référence pour comparer la sortie.


//: c14:ThreadGroup1.java
// Comment les groupes de threads contrôlent les priorités
// des threads qui les composent.

public class ThreadGroup1 {
  public static void main(String[] args) {
    // Récupère le thread system & imprime ses Info:
    ThreadGroup sys =
      Thread.currentThread().getThreadGroup();
    sys.list(); // (1)
    // Réduit la priorité du groupe de thread système:
    sys.setMaxPriority(Thread.MAX_PRIORITY - 1);
    // Augmente la priorité du thread principal:
    Thread curr = Thread.currentThread();
    curr.setPriority(curr.getPriority() + 1);
    sys.list(); // (2)
    // Essaie de créer un nouveau groupe avec une priorité maximum:
    ThreadGroup g1 = new ThreadGroup("#004488">"g1");
    g1.setMaxPriority(Thread.MAX_PRIORITY);
    // Essaie de créer un nouveau thread avec une priorité maximum:
    Thread t = new Thread(g1, "A");
    t.setPriority(Thread.MAX_PRIORITY);
    g1.list(); // (3)
    // Reduit la priorité maximum de g1, puis essaie
    // de l'augmenter:
    g1.setMaxPriority(Thread.MAX_PRIORITY - 2);
    g1.setMaxPriority(Thread.MAX_PRIORITY);
    g1.list(); // (4)
    // Essaie de créer un nouveau thread avec une priorité maximum:
    t = new Thread(g1, "B");
    t.setPriority(Thread.MAX_PRIORITY);
    g1.list(); // (5)
    // Diminue la priorité maximum en dessous
    // de la priorité par défaut du thread:
    g1.setMaxPriority(Thread.MIN_PRIORITY + 2);
    // Regarde la priorité d'un nouveau thread
    // avant et après son changement:
    t = new Thread(g1, "C");
    g1.list(); // (6)
    t.setPriority(t.getPriority() -1);
    g1.list(); // (7)
    // Fait de g2 un groupe de thread fils de g1 et
    // essaie d'augmenter sa priorité:
    ThreadGroup g2 = new ThreadGroup(g1, "#004488">"g2");
    g2.list(); // (8)
    g2.setMaxPriority(Thread.MAX_PRIORITY);
    g2.list(); // (9)
    // Ajoute un banc de nouveaux threads à g2:
    for (int i = 0; i < 5; i++)
      new Thread(g2, Integer.toString(i));
    // Montre des informations sur tous les groupes de threads
    // et threads:
    sys.list(); // (10)
    System.out.println("Starting all threads:");
    Thread[] all = new Thread[sys.activeCount()];
    sys.enumerate(all);
    for(int i = 0; i < all.length; i++)
      if(!all[i].isAlive())
        all[i].start();
    // Suspends & Arrête tous les threads de  
    // ce groupe et de ses sous-groupes:
    System.out.println("All threads started");
    sys.suspend(); // Deprecié en Java 2
    // On n'arrive jamais ici...
    System.out.println("All threads suspended");
    sys.stop(); //  Deprecié en Java 2
    System.out.println("All threads stopped");
  }
} ///:~

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