t
t
t
t
t t 14) Les Threads multiples
tttt
14) Les « Threads » multiples
Texte original t Traducteur : Cédric BABAULT
t
t
///
Ce chapitre contient 9 pages
1 2 3 4 5 6 7 8 9
\\\
t t t
t t t
t
t t t

Combining the thread with the main class

t

Combiner le thread avec la classe principale

t t t
In the example above you can see that the thread class is separate from the program’s main class. This makes a lot of sense and is relatively easy to understand. There is, however, an alternate form that you will often see used that is not so clear but is usually more concise (which probably accounts for its popularity). This form combines the main program class with the thread class by making the main program class a thread. Since for a GUI program the main program class must be inherited from either Frame or Applet, an interface must be used to paste on the additional functionality. This interface is called Runnable, and it contains the same basic method that Thread does. In fact, Thread also implements Runnable, which specifies only that there be a run( ) method. t Dans l'exemple ci-dessus, vous pouvez voir que la classe thread est séparée de la classe principale du programme. C'est la solution la plus sensé et c'est relativement facile à comprendre. Il existe toutefois une forme alternative qui vous verrez souvent utiliser, qui n'est pas aussi claire mais qui est souvent plus concise (ce qui augmente probablement sa popularité). Cette forme combine la classe du programme principal avec la classe thread en faisant de la classe du programme principal un thread. Comme pour un programme GUI la classe du programme principal doit hérité soit de Frame soit d'Applet, une interface doit être utilisée pour coller à la nouvelle fonctionnalité. Cette interface est appelée Runnable, elle contient la même méthode de base que Thread. En fait, Thread implémente aussi Runnable, qui spécifie seulement qu'il y a une méthode run()
t t t
The use of the combined program/thread is not quite so obvious. When you start the program, you create an object that’s Runnable, but you don’t start the thread. This must be done explicitly. You can see this in the following program, which reproduces the functionality of Counter2: t L'utilisation de la combinaison programme/thread n'est pas vraiment évidente. Quand vous démarrez le programme, vous créez un objet Runnable, mais vous ne démarrez pas le thread. Ceci doit être fait explicitement. C'est ce que vous pouvez voir dans le programme qui suit, qui reproduit la fonctionnalité de Counter2:
t t t
//: c14:Counter3.java
// Using the Runnable interface to turn the
// main class into a thread.
// <applet code=Counter3 width=300 height=100>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Counter3
    extends JApplet implements Runnable {
  private int count = 0;
  private boolean runFlag = true;
  private Thread selfThread = null;
  private JButton
    start = new JButton("Start"),
    onOff = new JButton("Toggle");
  private JTextField t = new JTextField(10);
  public void run() {
    while (true) {
      try {
        selfThread.sleep(100);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
      if(runFlag)
        t.setText(Integer.toString(count++));
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(selfThread == null) {
        selfThread = new Thread(Counter3.this);
        selfThread.start();
      }
    }
  }
  class OnOffL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      runFlag = !runFlag;
    }
  }
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t);
    start.addActionListener(new StartL());
    cp.add(start);
    onOff.addActionListener(new OnOffL());
    cp.add(onOff);
  }
  public static void main(String[] args) {
    Console.run(new Counter3(), 300, 100);
  }
} ///:~
t
//: c14:Counter3.java
// Utilisation de l'interface Runnable pour  
// transformer la classe principale en thread.
// <applet code=Counter3 width=300 height=100>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Counter3
    extends JApplet implements Runnable {
  private int count = 0;
  private boolean runFlag = true;
  private Thread selfThread = null;
  private JButton
    start = new JButton("Start"),
    onOff = new JButton("Toggle");
  private JTextField t = new JTextField(10);
  public void run() {
    while (true) {
      try {
        selfThread.sleep(100);
      } catch(InterruptedException e) {
        System.err.println("Interrupted");
      }
      if(runFlag)
        t.setText(Integer.toString(count++));
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(selfThread == null) {
        selfThread = new Thread(Counter3.this);
        selfThread.start();
      }
    }
  }
  class OnOffL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      runFlag = !runFlag;
    }
  }
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t);
    start.addActionListener(new StartL());
    cp.add(start);
    onOff.addActionListener(new OnOffL());
    cp.add(onOff);
  }
  public static void main(String[] args) {
    Console.run(new Counter3(), 300, 100);
  }
} ///:~
t t t
Now the run( ) is inside the class, but it’s still dormant after init( ) completes. When you press the start button, the thread is created (if it doesn’t already exist) in the somewhat obscure expression: t Maintenant run() est définie dans la classe, mais reste en sommeil après la fin de init(). Quand vous pressez le bouton start, le thread est créé (si il n'existe pas déjà) dans cette expression obscure:
t t t
new Thread(Counter3.this);
t
new Thread(Counter3.this);
t t t
When something has a
Runnable
interface, it simply means that it has a run( ) method, but
there’s nothing special about that—it doesn’t produce any
innate threading abilities, like those of a class inherited from Thread.
So to produce a thread from a Runnable object, you must create a separate
Thread object as shown above, handing the Runnable object to the
special Thread constructor. You can then call start( ) for
that thread:

t Quand une classe a une interface Runnable, cela signifie simplement qu'elle définit une méthode run(), mais n'entraîne rien d'autre de spécial —  contrairement à une classe héritée de thread elle n'a pas de capacité de threading naturelle. Donc pour produire un thread à partir d'un objet Runnable vous devez créer un objet Thread séparé comme ci-dessus, passer l'objet Runnable au constructeur spécial de Thread. Vous pouvez alors appeler start() sur ce thread:
t t t
selfThread.start();
t
selfThread.start();
t t t
This performs the usual initialization
and then calls run( ).
t L'initialisation usuelle est alors effectuée puis la méthode run() est appelé.
t t t
The convenient aspect about the
Runnable interface is that everything belongs to the same class. If you
need to access something, you simply do it without going through a separate
object. However, as you saw in the previous example, this access is just as easy
using an inner
class[70].
t L'aspect pratique de l'interface Runnable est que tout appartient à la même classe. Si vous avez besoin d'accéder à quelque chose, vous le faites simplement sans passer par un objet séparé. Cependant, comme vous avez pu le voir dans le précédent exemple, cet accès est aussi facile en utilisant des classes internes. .
t t t


Making many threads


t

Créer plusieurs threads

t t t
Consider the creation of many different
threads. You can’t do this with the previous example, so you must go back
to having separate classes inherited from Thread to encapsulate the
run( ). But this is a more general solution and easier to
understand, so while the previous example shows a coding style you’ll
often see, I can’t recommend it for most cases because it’s just a
little bit more confusing and less flexible.
t Considérons la création de plusieurs threads différents. Vous ne pouvez pas le faire avec l'exemple précédent, vous devez donc revenir à plusieurs classes séparées, héritant de Thread pour encapsuler run(). Il s'agit d'une solution plus générale et facile à comprendre. Bien que l'exemple précédent montre un style de codage que vous rencontrerez souvent, je ne vous le recommande pas pour la plupart des cas car ce style est juste un peu plus confus et moins flexible.
t t t
The following example repeats the form of
the examples above with counters and toggle buttons. But now all the information
for a particular counter, including the button and text field, is inside its own
object that is inherited from Thread. All the fields in Ticker are
private, which means that the Ticker implementation can be changed
at will, including the quantity and type of data components to acquire and
display information. When a Ticker object is created, the constructor
adds its visual components to the content pane of the outer
object:

t L'exemple suivant reprend la forme des exemples précédents avec des compteurs et des boutons bascules. Mais maintenant toute l'information pour un compteur particulier, le bouton et le champ texte inclus, est dans son propre objet qui hérite de Thread. Tous les champs de Ticker sont private, ce qui signifie que l'implémentation de Ticker peut changer à volonté, ceci inclus la quantité et le type des composants pour acquérir et afficher l'information. Quand un objet Ticker est créé, le constructeur ajoute ces composants au content pane de l'objet externe:
t t t
//: c14:Counter4.java
// By keeping your thread as a distinct class,
// you can have as many threads as you want.
// <applet code=Counter4 width=200 height=600>
// <param name=size value="12"></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Counter4 extends JApplet {
  private JButton start = new JButton("Start");
  private boolean started = false;
  private Ticker[] s;
  private boolean isApplet = true;
  private int size = 12;
  class Ticker extends Thread {
    private JButton b = new JButton("Toggle");
    private JTextField t = new JTextField(10);
    private int count = 0;
    private boolean runFlag = true;
    public Ticker() {
      b.addActionListener(new ToggleL());
      JPanel p = new JPanel();
      p.add(t);
      p.add(b);
      // Calls JApplet.getContentPane().add():
      getContentPane().add(p);
    }
    class ToggleL implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        runFlag = !runFlag;
      }
    }
    public void run() {
      while (true) {
        if (runFlag)
          t.setText(Integer.toString(count++));
        try {
          sleep(100);
        } catch(InterruptedException e) {
          System.err.println("Interrupted");
        }
      }
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(!started) {
        started = true;
        for (int i = 0; i < s.length; i++)
          s[i].start();
      }
    }
  }
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    // Get parameter "size" from Web page:
    if (isApplet) {
      String sz = getParameter("size");
      if(sz != null)
        size = Integer.parseInt(sz);
    }
    s = new Ticker[size];
    for (int i = 0; i < s.length; i++)
      s[i] = new Ticker();
    start.addActionListener(new StartL());
    cp.add(start);
  }
  public static void main(String[] args) {
    Counter4 applet = new Counter4();
    // This isn't an applet, so set the flag and
    // produce the parameter values from args:
    applet.isApplet = false;
    if(args.length != 0)
      applet.size = Integer.parseInt(args[0]);
    Console.run(applet, 200, applet.size * 50);
  }
} ///:~
t
//: c14:Counter4.java
// En conservant votre thread comme une classe distincte
// vous pouvez avoir autant de threads que vous voulez
// <applet code=Counter4 width=200 height=600>
// <param name=size value="12"></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Counter4 extends JApplet {
  private JButton start = new JButton("Start");
  private boolean started = false;
  private Ticker[] s;
  private boolean isApplet = true;
  private int size = 12;
  class Ticker extends Thread {
    private JButton b = new JButton(color="#004488">"Toggle");
    private JTextField t = new JTextField(10);
    private int count = 0;
    private boolean runFlag = color="#0000ff">true;
    public Ticker() {
      b.addActionListener(new ToggleL());
      JPanel p = new JPanel();
      p.add(t);
      p.add(b);
      // Appelle JApplet.getContentPane().add():
      getContentPane().add(p);
    }
    class ToggleL implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        runFlag = !runFlag;
      }
    }
    public void run() {
      while (true) {
        if (runFlag)
          t.setText(Integer.toString(count++));
        try {
          sleep(100);
        } catch(InterruptedException e) {
          System.err.println("Interrupted");
        }
      }
    }
  }
  class StartL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(!started) {
        started = true;
        for (int i = 0; i < s.length; i++)
          s[i].start();
      }
    }
  }
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    // Obtient le paramètre "size" depuis la page Web
    if (isApplet) {
      String sz = getParameter("size");
      if(sz != null)
        size = Integer.parseInt(sz);
    }
    s = new Ticker[size];
    for (int i = 0; i < s.length; i++)
      s[i] = new Ticker();
    start.addActionListener(new StartL());
    cp.add(start);
  }
  public static void main(String[] args) {
    Counter4 applet = new Counter4();
    // Ce n'est pas une applet, donc désactive le flag et
    // produit la valeur du paramètre depuis les arguments:
    applet.isApplet = false;
    if(args.length != 0)
      applet.size = Integer.parseInt(args[0]);
    Console.run(applet, 200, applet.size * 50);
  }
} ///:~
t t t
Ticker contains not only its threading equipment but also the way to control and display the thread. You can create as many threads as you want without explicitly creating the windowing components. t Ticker contient non seulement l'équipement pour le threading, mais aussi le moyen de contrôler et d'afficher le thread. Vous pouvez créer autant de threads que vous voulez sans créer explicitement les composants graphiques.
t t t
In Counter4 there’s an array of Ticker objects called s. For maximum flexibility, the size of this array is initialized by reaching out into the Web page using applet parameters. Here’s what the size parameter looks like on the page, embedded inside the applet tag: t On utilise dans Counter4 un tableau d'objets Ticker appelé s. Pour un maximum de flexibilité, la taille du tableau est initialisée en utilisant les paramètres de l'applet donnés dans la page Web. Voici ce à quoi ressemble le paramètre de taille sur la page, inclus dans le tag d'applet:
t t t
<param name=size value="20">
t
<param name=size value="20">
t t t
The
param,
name, and
value are all HTML
keywords. name is what you’ll be referring to in your program, and
value can be any string, not just something that resolves to a
number.
t Les mots param, name, et value sont tous des mots clés HTML. name est ce à quoi vous ferez référence dans votre programme, et value peut être n'importe quelle chaîne, pas seulement quelque chose qui s'analyse comme un nombre.
t t t
You’ll notice that the
determination of the size of the array s is done inside
init( ), and not as part of an inline definition of s. That
is, you cannot say as part of the class definition (outside of any
methods):

t Vous remarquerez que la détermination de la taille du tableau s est faite dans la méthode init(), et non comme une définition en ligne de s. En fait, vous ne pouvez pas écrire dans la définition de la classe (en dehors de toutes méthodes):
t t t
int size = Integer.parseInt(getParameter("size"));
Ticker[] s = new Ticker[size];
t
int size = Integer.parseInt(getParameter("#004488">"size"));
Ticker[] s = new Ticker[size];
t t t
You can compile this, but you’ll get a strange “null-pointer exception” at run-time. It works fine if you move the getParameter( ) initialization inside of init( ). The applet framework performs the necessary startup to grab the parameters before entering init( ). t Vous pouvez compiler ce code, mais vous obtiendrez une étrange « null-pointer exception » à l'exécution. Cela fonctionne correctement si vous déplacez l'initialisation avec getParameter() dans init(). Le framework de l'applet réalise les opérations nécessaires à l'initialisation pour récupérer les paramètres avant d'entrer dans init().
t t t
In addition, this code is set up to be either an applet or an application. When it’s an application the size argument is extracted from the command line (or a default value is provided). t En plus, ce code est prévu pour être soit une applet, soit une application. Si c'est une application l'argument size est extrait de la ligne de commande (ou une valeur par défaut est utilisée).
t t t
Once the size of the array is established, new Ticker objects are created; as part of the Ticker constructor the button and text field for each Ticker is added to the applet. t Une fois la taille du tableau établie, les nouveaux objets Ticker sont créés; en temps que partie du constructeur, les boutons et champs texte sont ajoutés à l'applet pour chaque Ticker.
t t t
Pressing the start button means looping through the entire array of Tickers and calling start( ) for each one. Remember, start( ) performs necessary thread initialization and then calls run( ) for that thread. t Presser le bouton start signifie effectuer une boucle sur la totalité du tableau de Tickers et appeler start() pour chacun d'eux. N'oubliez pas que start() réalise l'initialisation du thread nécessaire et appelle ensuite run() pour chaque thread.
t t t
The ToggleL listener simply inverts the flag in Ticker and when the associated thread next takes note it can react accordingly. t Le listener ToggleL inverse simplement le flag dans Ticker et quand le thread associé en prend note il peut réagir en conséquence.
t t t
One value of this example is that it allows you to easily create large sets of independent subtasks and to monitor their behavior. In this case, you’ll see that as the number of subtasks gets larger, your machine will probably show more divergence in the displayed numbers because of the way that the threads are served. t Un des intérêt de cet exemple est qu'il vous permet de créer facilement un grand jeux de sous-tâches indépendantes et de contrôler leur comportement. Avec ce programme, vous pourrez voir que si on augmente le nombre de sous-tâches, votre machine montrera probablement plus de divergence dans les nombres affichés à cause de la façon dont les threads sont servis.
t t t
You can also experiment to discover how important the sleep(100) is inside Ticker.run( ). If you remove the sleep( ), things will work fine until you press a toggle button. Then that particular thread has a false runFlag and the run( ) is just tied up in a tight infinite loop, which appears difficult to break during multithreading, so the responsiveness and speed of the program really bogs down. t Vous pouvez également expérimenter pour découvrir combien la méthode sleep(100) est importante dans Ticker.run(). Si vous supprimer sleep(), les choses vont bien fonctionner jusqu'à ce que vous pressiez un bouton. Un thread particulier à un runFlag faux et run() est bloquer dans une courte boucle infinie, qu'il parait difficile d'arrêter au cours du multithreading, la dynamique et la vitesse du programme le fait vraiment boguer. [so the responsiveness and speed of the program really bogs down.]
t t t

Daemon threads

t

Threads démons

t t t
A “daemon” thread is one that is supposed to provide a general service in the background as long as the program is running, but is not part of the essence of the program. Thus, when all of the non-daemon threads complete, the program is terminated. Conversely, if there are any non-daemon threads still running, the program doesn’t terminate. (There is, for instance, a thread that runs main( ).) t Un thread démon est un thread qui est supposé proposer un service général en tache de fond aussi longtemps que le programme tourne, mais il n'est pas l'essence du programme. Ainsi, quand tous les threads non démons s'achève, le programme se termine. A l'inverse, si des threads non démons tournent encore le programme ne se termine pas. (Il y a, par exemple, un thread qui tourne main().)
t t t
You can find out if a thread is a daemon by calling isDaemon( ), and you can turn the “daemonhood” of a thread on and off with setDaemon( ). If a thread is a daemon, then any threads it creates will automatically be daemons. t Vous pouvez savoir si un thread est un démon en appelant isDaemon(), et vous pouvez activer ou désactiver la « démonité » [« daemonhood »] d'un thread avec setDaemon(). Si un thread est un démon, alors toutes les threads qu'il créera seront automatiquement des démons.
t t t
The following example demonstrates daemon threads: t L'exemple suivant montre des threads démons:
t t t
//: c14:Daemons.java
// Daemonic behavior.
import java.io.*;

class Daemon extends Thread {
  private static final int SIZE = 10;
  private Thread[] t = new Thread[SIZE];
  public Daemon() {
    setDaemon(true);
    start();
  }
  public void run() {
    for(int i = 0; i < SIZE; i++)
      t[i] = new DaemonSpawn(i);
    for(int i = 0; i < SIZE; i++)
      System.out.println(
        "t[" + i + "].isDaemon() = "
        + t[i].isDaemon());
    while(true)
      yield();
  }
}

class DaemonSpawn extends Thread {
  public DaemonSpawn(int i) {
    System.out.println(
      "DaemonSpawn " + i + " started");
    start();
  }
  public void run() {
    while(true)
      yield();
  }
}

public class Daemons {
  public static void main(String[] args)
  throws IOException {
    Thread d = new Daemon();
    System.out.println(
      "d.isDaemon() = " + d.isDaemon());
    // Allow the daemon threads to
    // finish their startup processes:
    System.out.println("Press any key");
    System.in.read();
  }
} ///:~
t
//: c14:Daemons.java
// Un comportement démoniaque

import java.io.*;

class Daemon extends Thread {
  private static final int SIZE = 10;
  private Thread[] t = new Thread[SIZE];
  public Daemon() {
    setDaemon(true);
    start();
  }
  public void run() {
    for(int i = 0; i < SIZE; i++)
      t[i] = new DaemonSpawn(i);
    for(int i = 0; i < SIZE; i++)
      System.out.println(
        "t[" + i + "].isDaemon() = "
        + t[i].isDaemon());
    while(true)
      yield();
  }
}

class DaemonSpawn extends Thread {
  public DaemonSpawn(int i) {
    System.out.println(
      "DaemonSpawn " + i + " started");
    start();
  }
  public void run() {
    while(true)
      yield();
  }
}

public class Daemons {
  public static void main(String[] args)
  throws IOException {
    Thread d = new Daemon();
    System.out.println(
      "d.isDaemon() = " + d.isDaemon());
    // Autorise le thread démon à finir
    // son processus de démarrage
    System.out.println("Press any key");
    System.in.read();
  }
} ///:~
t t t
The Daemon thread sets its daemon flag to “true” and then spawns a bunch of other threads to show that they are also daemons. Then it goes into an infinite loop that calls yield( ) to give up control to the other processes. In an earlier version of this program, the infinite loops would increment int counters, but this seemed to bring the whole program to a stop. Using yield( ) makes the program quite peppy. t Le thread Daemon positionne son flag démon à « vrai », il engendre alors un groupe d'autre threads pour montrer qu'ils sont aussi des démons. Il tombe alors dans une boucle infinie qui appelle yield() pour rendre le contrôle aux autres processus. Dans une première version de ce programme, la boucle infinie incrémentait un compteur int, mais cela semble entraîner un arrêt du programme. Utiliser yield() rend le programme plutôt nerveux.
t t t
There’s nothing to keep the program from terminating once main( ) finishes its job, since there are nothing but daemon threads running. So that you can see the results of starting all the daemon threads, System.in is set up to read so the program waits for a keypress before terminating. Without this you see only some of the results from the creation of the daemon threads. (Try replacing the read( ) code with sleep( ) calls of various lengths to see this behavior.) t Il n'y a rien pour empêcher le programme de se terminer une fois que main() a fini sont travail, puisqu'il n'y a plus que des threads démons qui tournent. Vous pouvez donc voir le résultat du démarrage de tous les threads démons, on appelle read sur System.in pour que le programme attende qu'une touche soit pressée avant de s'arrêter. Sans cela vous ne voyez qu'une partie des résultats de la création des threads démons. (Essayez de remplacer l'appel à read() par des appels à sleep() de différents tailles pour voir ce qui se passe.)
t t t

Sharing limited resources

t

Partager des ressources limitées

t t t
You can think of a single-threaded program as one lonely entity moving around through your problem space and doing one thing at a time. Because there’s only one entity, you never have to think about the problem of two entities trying to use the same resource at the same time, like two people trying to park in the same space, walk through a door at the same time, or even talk at the same time. t Vous pouvez penser un programme à une seule thread comme une seule entité se déplaçant dans votre espace problème et n'effectuant qu'une seule chose à la fois. Puisqu'il y a seulement une entité, vous n'avez jamais penser au problème de deux entité essayant d'utiliser la même ressource en même temps, comme deux personnes essayant de se garer sur la même place, passer la même porte en même temps, ou encore parler en même temps.
t t t
With multithreading, things aren’t lonely anymore, but you now have the possibility of two or more threads trying to use the same limited resource at once. Colliding over a resource must be prevented or else you’ll have two threads trying to access the same bank account at the same time, print to the same printer, or adjust the same valve, etc. t Avec le multithreading, les chose ne sont plus seules, mais vous avez maintenant la possibilité d'avoir deux ou trois threads essayant d'utiliser la même ressource limitée à la fois. Les collisions sur une ressource doivent être évitées ou alors vous aurez deux threads essayant d'accéder au même compte bancaire en même temps, imprimer sur la même imprimante, ou ajuster la même soupape, etc...
t t t
t t t
t t
\\\
///
t t t
t
     
Sommaire Le site de Bruce Eckel