t
t
t
t
t t   14) Les Threads multiples
tttt
t
carrea) Préface carreb) Avant-propos carre1) Introduction sur les &laqo; objets » carre2) Tout est &laqo; objet » carre3) Contrôle du flux du programme carre4) Initialization & Cleanup carre5) Cacher l'implémentation carre6) Réutiliser les classes carre7) Polymorphisme carre8) Interfaces & classes internes carre9) Stockage des objets carre10) Error Handling with Exceptions carre11) Le système d’E/S de Java carre12) Identification dynamique de type carre13) Création de fenêtres & d'Applets 14) Les &laqo; Threads » multiples carre15) Informatique distribuée carreA) Passage et retour d'objets carreB) L'Interface Java Natif (JNI) carreC) Conseils pour une programation stylée en Java carreD) Resources
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

Synchronizing the counters

t

Synchroniser les compteurs

t t t
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:
t 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é:
t t t
//: 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); } } ///:~ t
//: 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);
  }
} ///:~
t t t
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.
t 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.
t t t
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.
t 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.
t t t
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:
t 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é:
t t t
synchronized(syncObject) { // This code can be accessed // by only one thread at a time } t
synchronized(syncObject) {
  // Ce code ne peut être accéder
  // que par un thread à la fois
}
t t t
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.
t 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é.
t t t
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:
t 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 à:
t t t
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"); } } } t
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");
      }
    }
  }
t t t
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( ).
t 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().
t t t
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.
t 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é.
t t t

Synchronized efficiency

t

Efficacité de la synchronisation

t t t
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.
t 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.
t t t

JavaBeans revisited

t

JavaBeans revisités

t t t
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:
t 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:
t t t
  1. 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.
  2. 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.
t
  1. 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.
  2. 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.
t t t
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:
t 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:
t t t
//: 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); } } ///:~ t
//: 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);
  }
} ///:~
t t t
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.
t 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.
t t t
t t t
t t
\\\
///
t t t
t
     
Sommaire Le site de Bruce Eckel