IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 14 - Les Threads multiples

pages : 1 2 3 4 5 6 7 8 9 

CBox2 est similaire à CBox: elle se paint avec une couleur choisie au hasard. Mais c'est tout ce qu'une CBox2 fait. Tout le threading a été déplacé dans CBoxList.

Le CBoxList pourrait aussi avoir hérité de Thread et avoir un objet membre de type ArrayList. Ce design a l'avantage que les méthodes add() et get() puissent avoir des arguments et un type de valeur de retour spécifiques au lieu de ceux de la classe générique Object. (Leurs noms pourraient aussi être changés en quelque chose de plus court.) Cependant, le design utilisé ici semble au premier coup d'œil nécessiter moins de code. En plus, on garde automatiquement tous les autres comportements d'un ArrayList. Avec tous les cast et parenthèses nécessaires pour get(), ça ne devrait pas être le cas dès que votre code central augmente.

Comme précédemment, quand vous implémentez Runnable vous ne disposez pas de tout l'équipement que vous avez avec Thread, donc vous devez créer un nouveau Thread et passer vous même à son constructeur pour avoir quelque chose à démarrer (par start()), comme vous pouvez le voir dans le constructeur de CBoxList et dans go(). La méthode run() choisit simplement un numéro d'élément au hasard dans la liste et appelle nextColor() pour cet élément ce qui provoque le sélection au hasard d'une nouvelle couleur.

En exécutant ce programme, vous voyez qu'effectivement il tourne et répond plus vite (par exemple, quand vous l'interrompez, il s'arrête plus rapidement), et il ne semble pas ralentir autant pour de grandes tailles de grilles. Ainsi, un nouveau facteur est ajouter à l'équation du threading: vous devez regarder pour voir si vous n'avez pas « trop de threads » (quelqu'en soit la signification pour votre programme et plate-forme en particulier, le ralentissement dans ColorBoxes apparaît comme provenant du fait qu'il n'y a qu'un thread qui répond pour toutes les colorations, et qu'il est ralenti par trop de requêtes). Si vous avez trop de threads, vous devez essayez d'utiliser des techniques comme celle ci-dessus pour « équilibrer » le nombre de threads dans votre programme. Si vous voyez des problèmes de performances dans un programme multithread vous avez maintenant plusieurs solutions à examiner:

  1. Avez vous assez d'appels à sleep(), yield(), et/ou wait()?
  2. Les appels à sleep() sont-ils assez longs?
  3. Faites vous tournez trop de threads?
  4. Avez vous essayé différentes plates-formes et JVMs?

Des questions comme celles-là sont la raison pour laquelle la programmation multithread est souvent considéré comme un art.

Résumé

Il est vital d'apprendre quand utiliser le multithreading et quand l'éviter. La principale raison pour l'utiliser est pour gérer un nombre de taches qui mélangées rendront plus efficace l'utilisation de l'ordinateur (y compris la possibilité de distribuer de façon transparente les tâches sur plusieurs CPUs) ou être plus pratique pour l'utilisateur. L'exemple classique de répartition de ressources est l'utilisation du CPU pendant les attentes d'entrées/sorties. L'exemple classique du coté pratique pour l'utilisateur est la surveillance d'un bouton « stop » pendant un long téléchargement.

Les principaux inconvénients du multithreading sont:

  1. Ralentissement en attente de ressources partagées
  2. Du temp CPU supplémentaire nécessaire pour gérer les threads
  3. Complexité infructueuse, comme l'idée folle d'avoir un thread séparé pour mettre à jour chaque élément d'un tableau
  4. Pathologies incluant starving [=mourir de faim!?], racing, et inter-blocage [deadlock]

Un avantage supplémentaire des threads est qu'ils substituent des contextes d'exécution « léger » (de l'ordre de 100 instructions) aux contextes d'exécutions lourds des processus (de l'ordre de 1000 instructions). Comme tous les threads d'un processus donné partage le même espace mémoire, un contexte léger ne change que l'exécution du programme et les variables locales. D'un autre coté, un changement de processus — le changement de contexte lourd —  doit échanger l'intégralité de l'espace mémoire.

Le threading c'est comme marcher dans un monde entièrement nouveau et apprendre un nouveau langage de programmation entier, ou au moins un nouveau jeu de concepts de langage. Avec l'apparition du support des threads dans beaucoup de systèmes d'exploitation de micro-ordinateurs, des extensions pour les threads sont aussi apparues dans les langages de programmations ou librairies. Dans tous les cas, la programmation de thread (1) semble mystérieuse et requiert un changement dans votre façon de penser la programmation; et (2) apparaît comme similaire au support de thread dans les autres langages, donc quand vous comprenez les threads, vous les comprenez dans une langue commune. Et bien que le support des threads puisse faire apparaître Java comme un langage plus compliqué, n'accuser pas Java. Les threads sont difficiles.

Une des plus grande difficultés des threads se produit lorsque plus d'un thread partagent une ressource — comme la mémoire dans un objet — et que vous devez être sûr que plusieurs threads n'essayent pas de lire et changer cette ressource en même temps. Cela nécessite l'utilisation judicieuse du mot clé synchronized, qui est un outil bien utile mais qui doit être bien compris parce qu'il peut introduire silencieusement des situations d'inter-blocage.

En plus, il y a un certain art dans la mise en application des threads. Java est conçu pour résoudre vos problèmes — au moins en théorie. (Créer des millions d'objets pour une analyse d'un ensemble fini d'élément dans l'ingénierie, par exemple, devrait être faisable en Java). Toutefois, il semble qu'il y ai une borne haute au nombre de threads que vous voudrez créer, parce que à un certain point un grand nombre de threads semble devenir lours. Ce point critique n'est pas dans les milliers comme il devrait être avec les objets, mais plutôt à quelques centaines, quelquefois moins que 100. Comme vous créez souvent seulement une poignée de threads pour résoudre un problème, c'est typiquement pas vraiment une limite, bien que dans un design plus général cela devient une contrainte.

Une importante conséquence non-évidente du threading est que, en raison de l'ordonnancement des threads, vous pouvez classiquement rendre vos applications plus rapides en insérant des appels à sleep() dans la boucle principale de run(). Cela fait définitivement ressembler ça à un art, en particulier quand la longueur du délai semble augmenter les performances. Bien sûr, la raison pour laquelle cela arrive est que des délais plus court peuvent causer la fin de sleep() de l'interruption du scheduler avant que le thread tournant soit prêt à passer au sleep, forçant le scheduler à l'arrêter et le redémarrer plus tard afin qu'il puisse finir ce qu'il était en train de faire puis de passer à sleep. Cela nécessite une réflexion supplémentaire pour réaliser quel désordre regne dans tout ça.

Une chose que vous devez notez comme manquant dans ce chapitre est un exemple d'animation, ce qui est une des chose les plus populaires faite avec des applets. Toutefois, une solution complète (avec du son) à ce problème vient est disponible avec le Java JDK (disponible sur java.sun.com) dans la section demo. En plus, nous pouvons nous attendre à un meilleur support d'animation comme partie des futures versions de Java, tandis que des solutions très différentes du Java, non programmables, pour les animations apparaissent sur le Web qui seront probablement supérieures aux approches traditionnelles. Pour des explications à propos du fonctionnement des animations Java, voyez Core Java 2 par Horstmann & Cornell, Prentice-Hall, 1997. Pour des discussions plus avancées sur le threading, voyez Concurrent Programming in Java de Doug Lea, Addison-Wesley, 1997, ou Java Threads de Oaks & Wong, O’Reilly, 1997.

Exercices

Les solutions des exercices sélectionnés peuvent être trouvées dan le document électronique The Thinking in Java Annotated Solution Guide, disponible pour une petite contribution sur www.BruceEckel.com.

  1. Faites hériter une classe de Thread et redéfinissez la méthode run(). Dans run(), affichez un message, puis appelez sleep(). Répétez cela trois fois, puis sortez de run(). Placer un message de démarrage dans le constructeur et redéfinissez finalize() pour afficher un message d'arrêt. Faites une classe thread séparée qui appelle System.gc() et System.runFinalization() dans run(), affichant également un message. Faites plusieurs objets threads des deux types et exécutez les pour voir ce qui ce passe.
  2. Modifiez Sharing2.java pour ajouter un bloc synchronized dans la méthode run() de TwoCounter à la place de la synchronisation sur l'intégralité de la méthode run().
  3. Créez deux sous-classes de Thread une avec un run() qui démarre, capture la référence à un second objet Thread et appelle alors wait(). Le run() de l'autre classe devrait appeler notifyAll() pour le premier thread après qu'un certain nombre de secondes se soit écoulé, ainsi le premier thread peut afficher un message.
  4. Dans Ticker2 de Counter5.java, supprimez le yield() et expliquez les résultats. Remplacez le yield() avec un sleep() et expliquez les résultats.
  5. Dans ThreadGroup1.java, remplacez l'appel à sys.suspend() par un appel à wait() pour le bon groupe de thread, entraînant une attente de deux secondes. Pour que ça marche correctement vous devez acquérir le verrou pour sys dans un bloc synchronized.
  6. Changez Daemons.java pour que main() ait un sleep() à la place d'un readLine(). Expérimentez avec différents temps pour le sleep pour voir ce qui se passe.
  7. Dans le chapitre 8, localisez l'exemple GreenhouseControls.java, qui est constitués de trois fichiers. Dans Event.java, la classe Event est basée sur l'observation du temps. Changez Event pour qu'il soit un Thread, et changez le reste du design pour qu'il fonctionne avec le nouvel Event basé sur Thread.
  8. Modifiez l'exercice 7 pour que la classe java.util.Timer trouvé dans le JDK 1.3 soit utilisée pour faire tourner le système.
  9. En commençant avec SineWave.java du Chapitre 13, créez un programme (une applet/application utilisant la classe Console ) qui dessine un signal sinus animé qui apparaît en défilant sur la fenêtre comme un oscilloscope, dirigeant l'animation avec un Thread. La vitesse de l'animation pourra être controller avec un contrôle java.swing.JSlider.
  10. Modifiez l'exercice 9 afin que de multiple signaux sinus puissent être contrôlés par des tags HTML ou des paramètres de la ligne de commande.
  11. Modifiez l'exercice 9 afin que la classe java.swing.Timer soit utilisée pour diriger l'animation. Notez la différence entre celle-ci et java.util.Timer.

[70] Runnable était dans Java 1.0, tandis que les classes internes n'ont été introduites que dans Java 1.1, qui devait partiellement compter pour l'existence de Runnable. De plus, les architectures multithread traditionelles se basent sur une fonction à exécuter plutôt qu'un objet. Ma préférence est toujours d'hériter de Thread si possible; cela parait plus clair et plus flexible pour moi.

[71] The Java Programming Language, de Ken Arnold et James Gosling, Addison-Wesley 1996 pp 179.

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par Cédric Babault ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 4 5 6 7 8 9 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java