 |
 |
14) Les Threads multiples |
|
 |
|
Texte original |
 |
Traducteur : Cédric BABAULT |
|
 |
|
 |
 |
 |
 |
 |
 |
|
 |
|
 |
 |
 |
Combining the thread with the main class
|
 |
Combiner le thread avec la classe principale
|
 |
 |
 |
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.
|
 |
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()
|
 |
 |
 |
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:
|
 |
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:
|
 |
 |
 |
//: 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); } } ///:~
|
 |
//: 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); } } ///:~
|
 |
 |
 |
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:
|
 |
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:
|
 |
 |
 |
new Thread(Counter3.this);
|
 |
new Thread(Counter3.this);
|
 |
 |
 |
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:
|
 |
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:
|
 |
 |
 |
selfThread.start();
|
 |
selfThread.start();
|
 |
 |
 |
This performs the usual initialization and then calls run( ).
|
 |
L'initialisation usuelle est
alors effectuée puis la méthode run() est appelé.
|
 |
 |
 |
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].
|
 |
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. .
|
 |
 |
 |
Making many threads
|
 |
Créer plusieurs threads
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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:
|
 |
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:
|
 |
 |
 |
//: 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); } } ///:~
|
 |
//: 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); } } ///:~
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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:
|
 |
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:
|
 |
 |
 |
<param name=size value="20">
|
 |
<param name=size value="20">
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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):
|
 |
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):
|
 |
 |
 |
int size = Integer.parseInt(getParameter("size")); Ticker[] s = new Ticker[size];
|
 |
int size = Integer.parseInt(getParameter("#004488">"size")); Ticker[] s = new Ticker[size];
|
 |
 |
 |
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( ).
|
 |
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().
|
 |
 |
 |
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).
|
 |
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).
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
The ToggleL listener simply
inverts the flag in Ticker and when the associated thread next takes note
it can react accordingly.
|
 |
Le listener ToggleL inverse simplement le flag dans Ticker et
quand le thread associé en prend note il peut réagir en
conséquence.
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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.
|
 |
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.]
|
 |
 |
 |
Daemon threads
|
 |
Threads démons
|
 |
 |
 |
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( ).)
|
 |
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().)
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
The following example demonstrates daemon
threads:
|
 |
L'exemple suivant montre des threads démons:
|
 |
 |
 |
//: 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(); } } ///:~
|
 |
//: 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(); } } ///:~
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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.)
|
 |
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.)
|
 |
 |
 |
Sharing limited resources
|
 |
Partager des ressources limitées
|
 |
 |
 |
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.
|
 |
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.
|
 |
 |
 |
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.
|
 |
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...
|
 |
 |
 |
 |
 |
 |
 |
 |
|
 |
 |
 |