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

Improperly accessing resources

t

Des ressources accédées improprement

t t t
Consider a variation on the counters that have been used so far in this chapter. In the following example, each thread contains two counters that are incremented and displayed inside run( ). In addition, there’s another thread of class Watcher that is watching the counters to see if they’re always equivalent. This seems like a needless activity, since looking at the code it appears obvious that the counters will always be the same. But that’s where the surprise comes in. Here’s the first version of the program:
t Considérons une variation sur les compteurs déjà utilisées plus haut dans ce chapitre. Dans l'exemple suivant, chaque thread contient deux compteurs incrémentés et affichés dans run(). En plus, un autre thread de la classe Watcher regarde les compteurs pour voir si ils restent toujours les même. Ceci apparaît comme une activité inutile puisqu'en regardant le code il apparaît évident que que les compteurs resteront toujours les même. Mais c'est là que la surprise arrive. Voici la première version du programme:
t t t
//: c14:Sharing1.java // Problems with resource sharing while threading. // <applet code=Sharing1 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 Sharing1 extends JApplet { 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; private TwoCounter[] s; 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; // Add the display components as a panel: 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 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 void synchTest() { Sharing1.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 JLabel("Access Count")); p.add(aCount); cp.add(p); } public static void main(String[] args) { Sharing1 applet = new Sharing1(); // 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:Sharing1.java
// Les problèmes avec le partage
// de ressource et les threads
// <applet code=Sharing1 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 Sharing1 extends JApplet {
  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;
  private TwoCounter[] s;
  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;
    // Ajoute les composants visuels comme un panel
    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 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 void synchTest() {
      Sharing1.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 JLabel("Access Count"));
    p.add(aCount);
    cp.add(p);
  }
  public static void main(String[] args) {
    Sharing1 applet = new Sharing1();
    // Ce n'est pas une applet, donc désactive le flag et
    // produit la valeur du paramètre depuis les arguments:
    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
As before, each counter contains its own display components: two text fields and a label that initially indicates that the counts are equivalent. These components are added to the content pane of the outer class object in the TwoCounter constructor.
t Comme avant, chaque compteur contient ses propres composants graphiques: deux champs textes et un label qui au départ indique que les comptes sont equivalents. Ces composants sont ajoutés au content pane de l'objet de la classe externe dans le constructeur de TwoCounter.
t t t
Because a TwoCounter thread is started via a keypress by the user, it’s possible that start( ) could be called more than once. It’s illegal for Thread.start( ) to be called more than once for a thread (an exception is thrown). You can see the machinery to prevent this in the started flag and the overridden start( ) method.
t Comme un thread TwoCounter est démarré par l'utilisateur en pressant un bouton, il est possible que start() puisse être appelé plusieurs fois. Il est illégal d'appeler Thread.start() plusieurs fois pour une même thread (une exception est lancée). Vous pouvez voir la machinerie éviter ceci avec le flag started et la méthode redéfinie start().
t t t
In run( ), count1 and count2 are incremented and displayed in a manner that would seem to keep them identical. Then sleep( ) is called; without this call the program balks because it becomes hard for the CPU to swap tasks.
t Dans run(), count1 and count2 sont incrémentés et affichés de manière à ce qu'ils devraient rester identiques. Ensuite sleep() est appelé; sans cet appel le programme feinte [balks] parce qu'il devient difficile pour le CPU de passer d'une tache à l'autre.
t t t
The synchTest( ) method performs the apparently useless activity of checking to see if count1 is equivalent to count2; if they are not equivalent it sets the label to “Unsynched” to indicate this. But first, it calls a static member of the class Sharing1 that increments and displays an access counter to show how many times this check has occurred successfully. (The reason for this will become apparent in later variations of this example.)
t La méthode synchTest() effectue l'activité apparemment inutile de vérifier si count1 est équivalent à count2; si ils ne sont pas équivalent elle positionne l'étiquette à « Unsynched » pour indiquer ceci. Mais d'abord, elle appelle une méthode statique de la classe Sharing1 qui incrémente et affiche un compteur d'accès pour montrer combien de fois cette vérification c'est déroulée avec succès. (La raison de ceci apparaîtra dans les prochaines variations de cet exemple.)
t t t
The Watcher class is a thread whose job is to call synchTest( ) for all of the TwoCounter objects that are active. It does this by stepping through the array that’s kept in the Sharing1 object. You can think of the Watcher as constantly peeking over the shoulders of the TwoCounter objects.
t La classe Watcher est un thread dont le travail est d'appeler synchTest() pour tous les objets TwoCounter actifs. Elle le fait en parcourant le tableau maintenu dans l'objet Sharing1. Vous pouvez voir le Watcher comme regardant constamment par dessus l'épaule des objets TwoCounter.
t t t
Sharing1 contains an array of TwoCounter objects that it initializes in init( ) and starts as threads when you press the “start” button. Later, when you press the “Watch” button, one or more watchers are created and freed upon the unsuspecting TwoCounter threads.
t Sharing1 contient un tableau d'objets TwoCounter qu'il initialise dans init() et démarre comme thread quand vous pressez le bouton « start ». Plus tard, quand vous pressez le bouton « Watch », un ou plusieurs watchers sont créés et freed upon the unsuspecting TwoCounter threads. [NDT: mon dico est gros mais j'y comprend rien...]
t t t
Note that to run this as an applet in a browser, your applet tag will need to contain the lines:
t Notez que pour faire fonctionner ceci en tant qu'applet dans un browser, votre tag d'applet devra contenir ces lignes:
t t t
<param name=size value="20"> <param name=watchers value="1"> t
<param name=size value="20">
<param name=watchers value="1">
t t t
You can experiment by changing the width, height, and parameters to suit your tastes. By changing the size and watchers you’ll change the behavior of the program. This program is set up to run as a stand-alone application by pulling the arguments from the command line (or providing defaults).
t Vous pouvez expérimenter le changement de la largeur (width), la hauteur (height), et des paramètres pour l'adapter à vos goûts. En changeant size et watchers vous changerez le comportement du programme. Ce programme est prévu pour fonctionner comme une application autonome en passant les arguments sur la ligne de commande (ou en utilisant les valeurs par défaut).
t t t
Here’s the surprising part. In TwoCounter.run( ), the infinite loop is just repeatedly passing over the adjacent lines:
t La surprise arrive ici. Dans TwoCounter.run(), la boucle infinie se répète en exécutant seulement les deux lignes suivantes:
t t t
t1.setText(Integer.toString(count1++)); t2.setText(Integer.toString(count2++)); t
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
t t t
(as well as sleeping, but that’s not important here). When you run the program, however, you’ll discover that count1 and count2 will be observed (by the Watchers) to be unequal at times! This is because of the nature of threads—they can be suspended at any time. So at times, the suspension occurs between the execution of the above two lines, and the Watcher thread happens to come along and perform the comparison at just this moment, thus finding the two counters to be different.
t (elle dort aussi mais ce n'est pas important ici). En exécutant le programme, vous découvrirez que count1 et count2 seront observés (par les Watchers) comme étant inégaux par moments. A ce moment, la suspension a eu lieu entre l'exécution des deux lignes précédentes, et le thread Watcher s'est exécuté et a effectuée la comparaison juste à ce moment, trouvant ainsi les deux compteurs différents.
t t t
This example shows a fundamental problem with using threads. You never know when a thread might be run. Imagine sitting at a table with a fork, about to spear the last piece of food on your plate and as your fork reaches for it, the food suddenly vanishes (because your thread was suspended and another thread came in and stole the food). That’s the problem that you’re dealing with.
t Cet exemple montre un problème fondamental de l'utilisation des threads. Vous ne savez jamais quand un threads sera exécuté. Imaginez-vous assis à une table avec une fourchette, prêt à piquer la dernière bouchée de nourriture dans votre assiette et comme votre fourchette est prête à la prendre, la bouchée disparaît soudainement (parce que votre thread a été suspendu et qu'un autre thread est venu voler votre nourriture). C'est ce problème qu'il faut traiter.
t t t
Sometimes you don’t care if a resource is being accessed at the same time you’re trying to use it (the food is on some other plate). But for multithreading to work, you need some way to prevent two threads from accessing the same resource, at least during critical periods.
t Quelques fois vous ne faites pas attention si une ressource est accéder en même temps que vous essayez de l'utiliser (la bouchée est sur une autre assiette). Mais pour que le multithreading fonctionne, vous avez besoin d'un moyen d'empêcher que deux threads accèdent à la même resource, particulièrement durant les périodes critiques.
t t t
Preventing this kind of collision is simply a matter of putting a lock on a resource when one thread is using it. The first thread that accesses a resource locks it, and then the other threads cannot access that resource until it is unlocked, at which time another thread locks and uses it, etc. If the front seat of the car is the limited resource, the child who shouts “Dibs!” asserts the lock.
t Prévoir ce type de collisions est simplement un problème de placer un verrou sur une ressource quand un thread y accède. Le premier thread qui accède à la ressource la verrouille, ensuite les autres threads ne peuvent plus accéder à cette ressource jusqu'à ce qu'elle soit déverrouillée, à chaque fois un autre thread la verrouille et l'utilise, etc. Si le siège avant d'une voiture est la ressource limitée, les enfants qui crient « à moi! » revendiquent le verrou.
t t t

How Java shares resources

t

Comment Java partage les ressources

t t t
Java has built-in support to prevent collisions over one kind of resource: the memory in an object. Since you typically make the data elements of a class private and access that memory only through methods, you can prevent collisions by making a particular method synchronized. Only one thread at a time can call a synchronized method for a particular object (although that thread can call more than one of the object’s synchronized methods). Here are simple synchronized methods:
t Java possède un support intégré pour prévoir les collisions sur un type de ressources: la memoire est un objet. Alors que vous rendez typiquement les éléments de données d'une classe private et accéder à cette mémoire seulement à travers des méthodes, vous pouvez prévoir les collisions en rendant une méthode particulière synchronized. Un seul thread à la fois peut appeler une méthode synchronized pour un objet particulier (bien que ce thread puisse appeler plus d'une des méthodes synchronized sur cet objet). Voici des méthodes synchronized simples:
t t t
synchronized void f() { /* ... */ } synchronized void g(){ /* ... */ } t
synchronized void f() { /* ... */ }
synchronized void g(){ /* ... */ }
t t t
Each object contains a single lock (also called a monitor) that is automatically part of the object (you don’t have to write any special code). When you call any synchronized method, that object is locked and no other synchronized method of that object can be called until the first one finishes and releases the lock. In the example above, if f( ) is called for an object, g( ) cannot be called for the same object until f( ) is completed and releases the lock. Thus, there’s a single lock that’s shared by all the synchronized methods of a particular object, and this lock prevents common memory from being written by more than one method at a time (i.e., more than one thread at a time).
t Chaque objet contient un seul lock (également appelé monitor) qui fait automatiquement partie de l'objet (vous n'avez pas à écrire de code spécial). Quand vous appeler une méthode synchronized, cet objet est verrouillé et aucune autre méthode synchronized de cet objet ne peut être appelé jusqu'à ce que la première se termine et libère le verrou. Dans l'exemple précédent, si f() est appelé pour un objet, g() ne peut pas être appelé pour le même objet jusqu'à ce que f() soit terminé et libère le verrou. Ainsi, il y a un seul verrou partagé par toutes les méthodes synchronized d'un objet particulier et ce verrou protège la mémoire commune de l'écriture par plus d'une méthode à un instant donné (i.e., plus d'un thread à la fois).
t t t
There’s also a single lock per class (as part of the Class object for the class), so that synchronized static methods can lock each other out from simultaneous access of static data on a class-wide basis.
t Il y a aussi un seul verrou par classe ( appartenant à l'objet Class pour la classe), ainsi les méthodes synchronized static peuvent verrouiller les autres empêchant un accès simultané aux données static sur la base de la classe.
t t t
Note that if you want to guard some other resource from simultaneous access by multiple threads, you can do so by forcing access to that resource through synchronized methods.
t Remarquez que si vous voulez protéger d'autres ressources contre un accès simultanée par des threads multiples, vous pouvez le faire en forçant l'accès à d'autre ressource en passant par des méthodes synchronized.
t t t
t t t
t t
\\\
///
t t t
t
     
Sommaire Le site de Bruce Eckel