 |
 |
14) Les Threads multiples |
|
 |
|
Texte original |
 |
Traducteur : Cédric BABAULT |
|
 |
|
 |
 |
 |
 |
 |
 |
|
 |
|
 |
 |
 |
Synchronizing the counters
|
 |
Synchroniser les compteurs
|
 |
 |
 |
Armed with this new keyword it appears
that the solution is at hand: we’ll simply use the synchronized
keyword for the methods in TwoCounter. The following example is the same
as the previous one, with the addition of the new keyword:
|
 |
Muni de ce nouveau mot clé la solution est entre nos mains: nous
utiliserons simplement le mot clé synchronized pour les méthodes de
TwoCounter. L'exemple suivant est le même que le précédent, avec en plus
le nouveau mot clé:
|
 |
 |
 |
//: c14:Sharing2.java // Using the synchronized keyword to prevent // multiple access to a particular resource. // <applet code=Sharing2 width=350 height=500> // <param name=size value="12"> // <param name=watchers value="15"> // </applet> import javax.swing.*; import java.awt.*; import java.awt.event.*; import com.bruceeckel.swing.*;
public class Sharing2 extends JApplet { TwoCounter[] s; private static int accessCount = 0; private static JTextField aCount = new JTextField("0", 7); public static void incrementAccess() { accessCount++; aCount.setText(Integer.toString(accessCount)); } private JButton start = new JButton("Start"), watcher = new JButton("Watch"); private boolean isApplet = true; private int numCounters = 12; private int numWatchers = 15;
class TwoCounter extends Thread { private boolean started = false; private JTextField t1 = new JTextField(5), t2 = new JTextField(5); private JLabel l = new JLabel("count1 == count2"); private int count1 = 0, count2 = 0; public TwoCounter() { JPanel p = new JPanel(); p.add(t1); p.add(t2); p.add(l); getContentPane().add(p); } public void start() { if(!started) { started = true; super.start(); } } public synchronized void run() { while (true) { t1.setText(Integer.toString(count1++)); t2.setText(Integer.toString(count2++)); try { sleep(500); } catch(InterruptedException e) { System.err.println("Interrupted"); } } } public synchronized void synchTest() { Sharing2.incrementAccess(); if(count1 != count2) l.setText("Unsynched"); } } class Watcher extends Thread { public Watcher() { start(); } public void run() { while(true) { for(int i = 0; i < s.length; i++) s[i].synchTest(); try { sleep(500); } catch(InterruptedException e) { System.err.println("Interrupted"); } } } } class StartL implements ActionListener { public void actionPerformed(ActionEvent e) { for(int i = 0; i < s.length; i++) s[i].start(); } } class WatcherL implements ActionListener { public void actionPerformed(ActionEvent e) { for(int i = 0; i < numWatchers; i++) new Watcher(); } } public void init() { if(isApplet) { String counters = getParameter("size"); if(counters != null) numCounters = Integer.parseInt(counters); String watchers = getParameter("watchers"); if(watchers != null) numWatchers = Integer.parseInt(watchers); } s = new TwoCounter[numCounters]; Container cp = getContentPane(); cp.setLayout(new FlowLayout()); for(int i = 0; i < s.length; i++) s[i] = new TwoCounter(); JPanel p = new JPanel(); start.addActionListener(new StartL()); p.add(start); watcher.addActionListener(new WatcherL()); p.add(watcher); p.add(new Label("Access Count")); p.add(aCount); cp.add(p); } public static void main(String[] args) { Sharing2 applet = new Sharing2(); // This isn't an applet, so set the flag and // produce the parameter values from args: applet.isApplet = false; applet.numCounters = (args.length == 0 ? 12 : Integer.parseInt(args[0])); applet.numWatchers = (args.length < 2 ? 15 : Integer.parseInt(args[1])); Console.run(applet, 350, applet.numCounters * 50); } } ///:~
|
 |
//: c14:Sharing2.java // Utilisant le mot clé synchronized pour éviter // les accès multiples à une ressource particulière. // <applet code=Sharing2 width=350 height=500> // <param name=size value="12"> // <param name=watchers value="15"> // </applet> import javax.swing.*; import java.awt.*; import java.awt.event.*; import com.bruceeckel.swing.*;
public class Sharing2 extends JApplet { TwoCounter[] s; private static int accessCount = 0; private static JTextField aCount = new JTextField("0", 7); public static void incrementAccess() { accessCount++; aCount.setText(Integer.toString(accessCount)); } private JButton start = new JButton("Start"), watcher = new JButton("Watch"); private boolean isApplet = true; private int numCounters = 12; private int numWatchers = 15;
class TwoCounter extends Thread { private boolean started = color="#0000ff">false; private JTextField t1 = new JTextField(5), t2 = new JTextField(5); private JLabel l = new JLabel("count1 == count2"); private int count1 = 0, count2 = 0; public TwoCounter() { JPanel p = new JPanel(); p.add(t1); p.add(t2); p.add(l); getContentPane().add(p); } public void start() { if(!started) { started = true; super.start(); } } public synchronized void run() { while (true) { t1.setText(Integer.toString(count1++)); t2.setText(Integer.toString(count2++)); try { sleep(500); } catch(InterruptedException e) { System.err.println("Interrupted"); } } } public synchronized void synchTest() { Sharing2.incrementAccess(); if(count1 != count2) l.setText("Unsynched"); } } class Watcher extends Thread { public Watcher() { start(); } public void run() { while(true) { for(int i = 0; i < s.length; i++) s[i].synchTest(); try { sleep(500); } catch(InterruptedException e) { System.err.println("Interrupted"); } } } } class StartL implements ActionListener { public void actionPerformed(ActionEvent e) { for(int i = 0; i < s.length; i++) s[i].start(); } } class WatcherL implements ActionListener { public void actionPerformed(ActionEvent e) { for(int i = 0; i < numWatchers; i++) new Watcher(); } } public void init() { if(isApplet) { String counters = getParameter("size"); if(counters != null) numCounters = Integer.parseInt(counters); String watchers = getParameter("watchers"); if(watchers != null) numWatchers = Integer.parseInt(watchers); } s = new TwoCounter[numCounters]; Container cp = getContentPane(); cp.setLayout(new FlowLayout()); for(int i = 0; i < s.length; i++) s[i] = new TwoCounter(); JPanel p = new JPanel(); start.addActionListener(new StartL()); p.add(start); watcher.addActionListener(new WatcherL()); p.add(watcher); p.add(new Label("Access Count")); p.add(aCount); cp.add(p); } public static void main(String[] args) { Sharing2 applet = new Sharing2(); // Ce n'est pas une applet, donc place le flag et // récupère les valeurs de paramètres depuis args: applet.isApplet = false; applet.numCounters = (args.length == 0 ? 12 : Integer.parseInt(args[0])); applet.numWatchers = (args.length < 2 ? 15 : Integer.parseInt(args[1])); Console.run(applet, 350, applet.numCounters * 50); } } ///:~
|
 |
 |
 |
You’ll notice that both
run( ) and synchTest( ) are synchronized. If you
synchronize only one of the methods, then the other is free to ignore the object
lock and can be called with impunity. This is an important point: Every method
that accesses a critical shared resource must be synchronized or it
won’t work right.
|
 |
Vous noterez que les deux méthodes run() et
synchTest() sont synchronized. Si vous synchronizez seulement une des
méthodes, alors l'autre est libre d'ignorer l'objet verrouillé et peut être
appelé en toute impunité. C'est un point important: chaque méthode qui
accède à une ressource partagée critique doit être synchronized
ou ça ne fonctionnera pas correctement.
|
 |
 |
 |
Now a new issue arises. The
Watcher can never get a peek at what’s going on because the entire
run( ) method has been synchronized, and since
run( ) is always running for each object the lock is always tied up
and synchTest( ) can never be called. You can see this because the
accessCount never changes.
|
 |
Maintenant un nouveau problème apparaît. Le Watcher ne
peut jamais voir [get a peek] ce qui ce passe parce que la méthode run() est
entièrement synchronized, et comme run() tourne toujours pour chaque objet le
verrou est toujours fermé, synchTest() ne peut jamais être appelé. Vous
pouvez le voir parce que accessCount ne change jamais.
|
 |
 |
 |
What we’d like for this example is
a way to isolate only part of the code inside run( ). The
section of code you want to isolate this way is called a
critical section and you
use the synchronized keyword in a different way to set up a critical
section. Java supports critical sections with the
synchronized block; this time synchronized
is used to specify the object whose lock is being used to synchronize the
enclosed code:
|
 |
Ce que nous aurions voulu pour cet exemple est un moyen d'isoler seulement
une partie du code de run(). La section de code que vous voulez isoler de cette
manière est appelé une section critique et vous utilisez le mot clé
synchronized d'une manière différente pour créer une section critique.
Java supporte les sections critiques à l'aide d'un synchronized block; cette fois
synchronized est utilisé pour spécifier l'objet sur lequel le verrou est
utilisé pour synchronizer le code encapsulé:
|
 |
 |
 |
synchronized(syncObject) { // This code can be accessed // by only one thread at a time }
|
 |
synchronized(syncObject) { // Ce code ne peut être accéder // que par un thread à la fois }
|
 |
 |
 |
Before the synchronized block can be
entered, the lock must be acquired on syncObject. If some other thread
already has this lock, then the block cannot be entered until the lock is given
up.
|
 |
Avant l'entrée dans le bloc synchronisé, le verrou doit
être acquis sur syncObject. Si d'autres threads possède déjà le
verrou, l'entrée dans le bloc est impossible jusqu'à ce que le verrou soit
libéré.
|
 |
 |
 |
The Sharing2 example can be
modified by removing the synchronized keyword from the entire
run( ) method and instead putting a synchronized block around
the two critical lines. But what object should be used as the lock? The one that
is already respected by synchTest( ), which is the current object
(this)! So the modified run( ) looks like
this:
|
 |
L'exemple Sharing2 peut être modifié en supprimant le
mot clé synchronized de la méthode run() et de mettre à la place
un bloc synchronized autour des deux lignes critiques. Mais quel objet devrait être
utilisé comme verrou? Celui qui est déjà respecté par
synchTest(), qui est l'objet courant (this)! Ainsi la méthode run()
modifié ressemble à:
|
 |
 |
 |
public void run() { while (true) { synchronized(this) { t1.setText(Integer.toString(count1++)); t2.setText(Integer.toString(count2++)); } try { sleep(500); } catch(InterruptedException e) { System.err.println("Interrupted"); } } }
|
 |
public void run() { while (true) { synchronized(this) { t1.setText(Integer.toString(count1++)); t2.setText(Integer.toString(count2++)); } try { sleep(500); } catch(InterruptedException e) { System.err.println("Interrupted"); } } }
|
 |
 |
 |
This is the only change that must be made
to Sharing2.java, and you’ll see that while the two counters are
never out of synch (according to when the Watcher is allowed to look at
them), there is still adequate access provided to the Watcher during the
execution of run( ).
|
 |
C'est le seul changement qui doit être fait à
Sharing2.java, et vous verrez que bien que les compteurs ne soit jamais
désynchronisés (d'après ce que Watcher est autorisé à
voir d'eux), il y a toujours un accès adéquat fourni au Watcher pendant
l'exécution de run().
|
 |
 |
 |
Of course, all synchronization depends on
programmer diligence: every piece of code that can access a shared resource must
be wrapped in an appropriate synchronized block.
|
 |
Bien sûr, toutes les synchronisations dépendent de la
diligence du programmeur: chaque morceau de code qui peut accéder à une ressource
partagée doit être emballé dans un bloc synchronisé
approprié.
|
 |
 |
 |
Synchronized efficiency
|
 |
Efficacité de la synchronisation
|
 |
 |
 |
Since having two methods write to the
same piece of data never sounds like a particularly good idea, it might
seem to make sense for all methods to be automatically synchronized and
eliminate the synchronized keyword altogether. (Of course, the example
with a synchronized run( ) shows that this wouldn’t work
either.) But it turns out that acquiring a lock is not a cheap
operation—it multiplies the cost of a method call (that is, entering and
exiting from the method, not executing the body of the method) by a minimum of
four times, and could be much more depending on your implementation. So if you
know that a particular method will not cause contention problems it is expedient
to leave off the synchronized keyword. On the other hand, leaving off the
synchronized keyword because you think it is a performance bottleneck,
and hoping that there aren’t any collisions is an invitation to
disaster.
|
 |
Comme avoir deux méthodes écrivant dans le même morceau
de données n'apparaît jamais comme une bonne idée, il semblerait avoir
du sens que toutes les méthodes soit automatiquement synchronized et
d'éliminer le mot clé synchronized ailleurs. (Bien sûr, l'exemple avec
synchronized run() montre que ça ne fonctionnerait pas.) Mais il faut savoir
qu'acquérir un verrou n'est pas une opération légère; cela multiplie le
coût de l'appel de méthode (c'est à dire entrer et sortir de la méthode,
pas exécuter le corps de cette méthode) par au moins quatre fois, et peut être
très différent suivant votre implémentation. Donc si vous savez qu'une
méthode particulière ne posera pas de problèmes particuliers il est opportun
de ne pas utiliser le mot clé synchronized. D'un autre coté, supprimer le mot
clé synchronized parce que vous pensez que c'est un goulot d'étranglement pour
les performances en espérant qu'il n'y aura pas de collisions est une invitation au
désastre.
|
 |
 |
 |
JavaBeans revisited
|
 |
JavaBeans revisités
|
 |
 |
 |
Now that you understand synchronization,
you can take another look at
JavaBeans.
Whenever you create a Bean, you must assume that it will run in a multithreaded
environment. This means that:
|
 |
Maintenant que vous comprenez la synchronisation, vous pouvez avoir un
autre regard sur les Javabeans. Quand vous créez un Bean, vous devez assumez qu'il sera
exécuté dans un environnement multithread. Ce qui signifie que:
|
 |
 |
 |
- Whenever possible, all the
public methods of a Bean should be synchronized. Of course, this
incurs the synchronized run-time overhead. If that’s a problem,
methods that will not cause problems in critical sections can be left
un-synchronized, but keep in mind that this is not always obvious.
Methods that qualify tend to be small (such as getCircleSize( ) in
the following example) and/or “atomic,” that is, the method call
executes in such a short amount of code that the object cannot be changed during
execution. Making such methods un-synchronized might not have a
significant effect on the execution speed of your program. You might as well
make all public methods of a Bean synchronized and remove the
synchronized keyword only when you know for sure that it’s
necessary and that it makes a
difference.
- When
firing a multicast event to a bunch of listeners interested in that event, you
must assume that listeners might be added or removed while moving through the
list.
|
 |
-
Autant que possible, toutes les méthodes public d'un Bean
devront être synchronized. Bien sûr, cela implique un désagrément
lié à l'augmentation de temps d'exécution du à synchronized. Si
c'est un problème, les méthodes qui ne poseront pas de problème de sections
critiques peuvent être laissées non-synchronized, mais gardez en mémoire
que ce n'est pas toujours aussi évident. Les méthodes qui donne l'accès aux
attributs ont tendance à être petite (comme getCircleSize() dans l'exemple
suivant) et/ou « atomique » en fait, l'appel de méthode exécute
un si petit code que l'objet ne peut pas être changé durant l'exécution. Rendre
ce type de méthodes non-synchronized ne devrait pas avoir d'effet important sur la
vitesse d'exécution de votre programme. Vous devriez de même rendre toutes les
méthodes public d'un Bean synchronized et supprimer le mot clé
synchronized seulement quand vous savez avec certitude que c'est nécessaire et que
ça fera une différence.
-
Quand vous déclenchez un multicast event à un banc de
listeners intéressé par cet événement, vous devez vous assurer que tous
les listeners seront ajoutés et supprimés durant le déplacement dans la
liste.
|
 |
 |
 |
The first point is
fairly easy to deal with, but the second point requires a little more thought.
Consider the BangBean.java example presented in the last chapter. That
ducked out of the multithreading question by ignoring the synchronized
keyword (which hadn’t been introduced yet) and making the event unicast.
Here’s that example modified to work in a multithreaded environment and to
use multicasting for events:
|
 |
Le premier point est assez facile à comprendre, mais le second point
exige un petit effort. Considérez l'exemple BangBean.java presenté dans le
précédent chapitre. Il évitait la question du multithreading en ignorant le
mot clé synchronized (qui n'avait pas été encore introduit) et en
rendant l'événement unicast. Voici cet exemple modifié pour fonctionner dans
un environnement multi-tâche et utilisant le multicasting pour les
événements:
|
 |
 |
 |
//: c14:BangBean2.java // You should write your Beans this way so they // can run in a multithreaded environment. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import java.io.*; import com.bruceeckel.swing.*;
public class BangBean2 extends JPanel implements Serializable { private int xm, ym; private int cSize = 20; // Circle size private String text = "Bang!"; private int fontSize = 48; private Color tColor = Color.red; private ArrayList actionListeners = new ArrayList(); public BangBean2() { addMouseListener(new ML()); addMouseMotionListener(new MM()); } public synchronized int getCircleSize() { return cSize; } public synchronized void setCircleSize(int newSize) { cSize = newSize; } public synchronized String getBangText() { return text; } public synchronized void setBangText(String newText) { text = newText; } public synchronized int getFontSize() { return fontSize; } public synchronized void setFontSize(int newSize) { fontSize = newSize; } public synchronized Color getTextColor() { return tColor; } public synchronized void setTextColor(Color newColor) { tColor = newColor; } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.black); g.drawOval(xm - cSize/2, ym - cSize/2, cSize, cSize); } // This is a multicast listener, which is // more typically used than the unicast // approach taken in BangBean.java: public synchronized void addActionListener(ActionListener l) { actionListeners.add(l); } public synchronized void removeActionListener(ActionListener l) { actionListeners.remove(l); } // Notice this isn't synchronized: public void notifyListeners() { ActionEvent a = new ActionEvent(BangBean2.this, ActionEvent.ACTION_PERFORMED, null); ArrayList lv = null; // Make a shallow copy of the List in case // someone adds a listener while we're // calling listeners: synchronized(this) { lv = (ArrayList)actionListeners.clone(); } // Call all the listener methods: for(int i = 0; i < lv.size(); i++) ((ActionListener)lv.get(i)) .actionPerformed(a); } class ML extends MouseAdapter { public void mousePressed(MouseEvent e) { Graphics g = getGraphics(); g.setColor(tColor); g.setFont( new Font( "TimesRoman", Font.BOLD, fontSize)); int width = g.getFontMetrics().stringWidth(text); g.drawString(text, (getSize().width - width) /2, getSize().height/2); g.dispose(); notifyListeners(); } } class MM extends MouseMotionAdapter { public void mouseMoved(MouseEvent e) { xm = e.getX(); ym = e.getY(); repaint(); } } public static void main(String[] args) { BangBean2 bb = new BangBean2(); bb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ System.out.println("ActionEvent" + e); } }); bb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ System.out.println("BangBean2 action"); } }); bb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ System.out.println("More action"); } }); Console.run(bb, 300, 300); } } ///:~
|
 |
//: c14:BangBean2.java // Vous devriez écrire vos Beans de cette façon pour qu'ils // puissent tournés dans un environement multithread. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import java.io.*; import com.bruceeckel.swing.*;
public class BangBean2 extends JPanel implements Serializable { private int xm, ym; private int cSize = 20; // Taille du cercle private String text = "Bang!"; private int fontSize = 48; private Color tColor = Color.red; private ArrayList actionListeners = new ArrayList(); public BangBean2() { addMouseListener(new ML()); addMouseMotionListener(new MM()); } public synchronized int getCircleSize() { return cSize; } public synchronized void setCircleSize(int newSize) { cSize = newSize; } public synchronized String getBangText() { return text; } public synchronized void setBangText(String newText) { text = newText; } public synchronized int getFontSize() { return fontSize; } public synchronized void setFontSize(int newSize) { fontSize = newSize; } public synchronized Color getTextColor() { return tColor; } public synchronized void setTextColor(Color newColor) { tColor = newColor; } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.black); g.drawOval(xm - cSize/2, ym - cSize/2, cSize, cSize); } // C'est un listener multicast, qui est // plus typiquement utilisé que l'approche // unicast utilisée dans BangBean.java: public synchronized void addActionListener(ActionListener l) { actionListeners.add(l); } public synchronized void removeActionListener(ActionListener l) { actionListeners.remove(l); } // Remarquez qu'elle n'est pas synchronized: public void notifyListeners() { ActionEvent a = new ActionEvent(BangBean2.this, ActionEvent.ACTION_PERFORMED, null); ArrayList lv = null; // Effectue une copie profonde de la liste au cas où // quelqu'un ajouterait un listener pendant que nous // appelons les listeners: synchronized(this) { lv = (ArrayList)actionListeners.clone(); } // Apelle toutes les méthodes listeners: for(int i = 0; i < lv.size(); i++) ((ActionListener)lv.get(i)) .actionPerformed(a); } class ML extends MouseAdapter { public void mousePressed(MouseEvent e) { Graphics g = getGraphics(); g.setColor(tColor); g.setFont( new Font( "TimesRoman", Font.BOLD, fontSize)); int width = g.getFontMetrics().stringWidth(text); g.drawString(text, (getSize().width - width) /2, getSize().height/2); g.dispose(); notifyListeners(); } } class MM extends MouseMotionAdapter { public void mouseMoved(MouseEvent e) { xm = e.getX(); ym = e.getY(); repaint(); } } public static void main(String[] args) { BangBean2 bb = new BangBean2(); bb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ System.out.println("ActionEvent" + e); } }); bb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ System.out.println("BangBean2 action"); } }); bb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ System.out.println("More action"); } }); Console.run(bb, 300, 300); } } ///:~
|
 |
 |
 |
Adding synchronized to the methods
is an easy change. However, notice in
addActionListener( ) and
removeActionListener( ) that the
ActionListeners are now added to and removed from an ArrayList, so
you can have as many as you want.
|
 |
Ajouter synchronized aux méthodes est un changement facile.
Toutefois, motez que dans addActionListener() et removeActionListener() que les
ActionListeners sont maintenant ajoutés et supprimés d'une ArrayList,
ainsi vous pouvez en avoir autant que vous voulez.
|
 |
 |
 |
 |
 |
 |
 |
 |
|
 |
 |
 |