JAVA : Gestion des threads

23 janvier 2013 rdorigny 0 commentaires

La programmation par thread se développe petit à petit. Déjà en C sous Unix, il y avait une bonne base avec de nombreux outils comme les mutex, sémaphores, spinlock, etc.... le tout plutôt orienté programmation système, temps réels et drivers.

JAVA qui lui est orienté application, propose également ce type de programmation. Nous étudierons dans ce chapitre les concepts de la programmation JAVA sous forme de thread, qui est beaucoup plus simple par rapport au C.

Elle est possible même dans le cas d'une machine mono-processeur, le temps d'activité sera partagée par la machine virtuelle entre les différents threads s'il y en a plusieurs.

1)La Classe Thread

1.1)Présentation

Java propose la classe Thread pour créer des instances. C'est à dire:
  • 1) Créer une classe X et la faire hériter de la classe Thread,
  • 2) Créer dans cette classe la méthode run(), qui décrit l'activité du thread,
  • 3) Instancier X et utiliser la méthode start() pour créer le processus.
  • 1.2)Exemple

    Nous allons créer deux Thread qui vont afficher plusieurs fois des choses différentes avec un délais d'attente différent également.

    public class example_thread { public static void main(String[] args) { Affiche a1=new Affiche("Thread1n", 4, 5); Affiche a2=new Affiche("Thread2n", 4, 10); a1.start(); a2.start(); } } class Affiche extends Thread{ public String s; public long wait; public int number; public Affiche(String s,int number,long wait){ this.s=s; this.wait=wait; this.number=number; } public void run(){ try { for (int i=0;i<number;i++){ System.out.print(s); sleep(wait); } } catch (Exception e) {} } }

    Notre programme affichera:
    Thread2 Thread1 Thread2 Thread1 Thread1 Thread2 Thread2

    Après analyse du résultat, on observe le fonctionnement simultanée des deux threads.

    2)L'interface Runnable

    Le problème de JAVA est que l'héritage multiple n'existe pas, et il est judicieux d'utiliser une interface plutôt que d'hériter. L'interface Runnable dérive de la classe Thread, elle implémente la méthode run() comme précédemment.
    public class example_thread2 { public static void main(String[] args) { Affiche a1=new Affiche("Thread1n", 4, 5); Affiche a2=new Affiche("Thread2n", 4, 10); Thread t1=new Thread(a1); Thread t2=new Thread(a2); t1.start(); t2.start(); } } class Affiche implements Runnable{ public String s; public long wait; public int number; public Affiche(String s,int number,long wait){ this.s=s; this.wait=wait; this.number=number; } public void run(){ try { for (int i=0;i<number;i++){ System.out.print(s); Thread.sleep(wait); } } catch (Exception e) {} } }

    Notre programme affichera alors:
    Thread2 Thread1 Thread2 Thread1 Thread2 Thread1 Thread1 Thread2 Thread2

    3)Interrompre un Thread

    3.1)Arrêt des thread entre eux

    Il est possible de demander à un thread d'en arrêter un autre. Pour cela le processus t1 fait une demande d'interruption à t2 par t2.interrupt(), et t2 consulte régulièrement un indicateur de demande d'interruption par la méthode statique interrupted() ou la méthode isInterrupted(). Aussi, t2 choisit de s'interrompre (il peut ne pas le faire) et à la possibilité de faire des choses avant, comme la libération des espaces mémoires. Pour interrompre le thread, il suffit d'appeler la fonction return; .

    A noter que la méthode isInterrupted() teste si le processus passé en variable a été terminé.

    3.2)Exemple d'interruption de thread

    import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class example_thread2 { public static void main(String[] args) { Affiche a1=new Affiche("Thread1n", 5000); Affiche a2=new Affiche("Thread2n", 10000); a1.start(); a2.start(); String str=lireString(); a1.interrupt(); System.out.println("Interruption du premier processus"); str=lireString(); a2.interrupt(); System.out.println("Interruption du deuxième processus"); } public static String lireString(){//lecture d'une chaine String ligne_lue=null; try{ InputStreamReader lecteur=new InputStreamReader(System.in); BufferedReader entree=new BufferedReader(lecteur); ligne_lue=entree.readLine(); } catch(IOException err){ System.exit(0); } return ligne_lue; } } class Affiche extends Thread{ private String s; private long wait; public Affiche(String s,long wait){ this.s=s; this.wait=wait; } public void run(){ try { while (true){ if (interrupted()) return; //Fin du processus System.out.print(s); sleep(wait); } } catch (Exception e) {} } }

    Notre programme affichera alors:
    Thread1 Thread2 Thread1 Interruption du premier processus Thread2 Thread2 Thread2 Interruption du deuxième processus

    3.3)Thread démon

    Un Thread démon (daemon en anglais) est un programme qui tourne en tâche de fond, généralement en écoute d'une mission à réaliser au fil de l'eau. Comme en C sous Linux, le daemon est rattaché au processus père qui l'a créé, et si le processus père s'arrête, il provoquera l'arrêt automatique des daemons associés. Il n'y a pas de reprise par un processus racine.

    Pour créer un daemon, il suffit d'appeler la méthode setDaemon(true) sur l'instance de l'objet Thread juste avant de lancer un start(). A noter qu'il existe une méthode isDaemon qui teste si l'instance est un démon.

    Le mieux est de voir l'exemple ci-dessous:
    import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class example_thread2 { public static void main(String[] args) { Affiche a1=new Affiche("Thread1n", 5000); Affiche a2=new Affiche("Thread2n", 10000); a1.setDaemon(true); a1.start(); a2.setDaemon(true); a2.start(); String str=lireString(); a1.interrupt(); System.out.println("Interruption du premier processus"); str=lireString(); a2.interrupt(); System.out.println("Interruption du deuxième processus"); } public static String lireString(){//lecture d'une chaine String ligne_lue=null; try{ InputStreamReader lecteur=new InputStreamReader(System.in); BufferedReader entree=new BufferedReader(lecteur); ligne_lue=entree.readLine(); } catch(IOException err){ System.exit(0); } return ligne_lue; } } class Affiche extends Thread{ private String s; private long wait; public Affiche(String s,long wait){ this.s=s; this.wait=wait; } public void run(){ try { while (true){ if (interrupted()) return; //Fin du processus System.out.print(s); sleep(wait); } } catch (Exception e) {} } }

    Pour arrêter un daemon (ou un Thread), il est possible d'utiliser la mèthode destroy() même si son son utilisation n'est pas conseillé. Autre solution faire un System.exit .

    4)La synchronisation des Threads

    Nous avons vu précédemment comment créer des programmes qui fonctionnent simultanément. La problématique dans ce type de programmation consiste à gérer les accès concurrentiels aux ressources partagées comme les variables globales.

    4.1)Les méthodes synchronisées

    Ce concept permet de gérer l’accès concurrentiel aux objets. L'idée est de bloquer un thread pour permettre à un autre de travailler. On se place ici dans le cadre d'un objet qui est partagé et utilisé par plusieurs threads. Il faut donc un mécanisme qui le permet en toute sécurité. Java propose donc le mot clé synchronized pour permettre au système de synchroniser les d'accès aux variables. A chaque instant, un seul objet peut accéder à l'objet partagé.

    Prenons l'exemple d'un objet partagé qui propose juste deux variables, un String et un entier. Créons deux Thread qui incrémentent l'entier et affichent le avec le compteur.
    import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class example_thread2 { public static void main(String[] args) { Compteur c=new Compteur("Le compteur est à : ", 0); Affiche a1=new Affiche(c,5000); Affiche a2=new Affiche(c,5000); a1.start(); a2.start(); String str=lireString(); a1.interrupt();a2.interrupt(); System.out.println("Interruption des processus"); } public static String lireString(){//lecture d'une chaine String ligne_lue=null; try{ InputStreamReader lecteur=new InputStreamReader(System.in); BufferedReader entree=new BufferedReader(lecteur); ligne_lue=entree.readLine(); } catch(IOException err){ System.exit(0); } return ligne_lue; } } class Compteur{ private String str; private int cpt=0; public Compteur(String str, int cpt) { this.str = str; this.cpt = cpt; } public synchronized String getStr() { return str; } public synchronized void setStr(String str) { this.str = str; } public synchronized int getCpt() { return cpt; } public synchronized void setCpt(int cpt) { this.cpt = cpt; } public synchronized void Inc() { this.cpt++; System.out.println(this.getStr()+" "+this.getCpt()); } } class Affiche extends Thread{ private Compteur compt; private long wait; public Affiche(Compteur c, long wait) { this.compt = c; this.wait = wait; } public void run(){ try { while (true){ if (interrupted()) return; //Fin du processus compt.Inc(); //System.out.println(compt.getStr()+" "+compt.getCpt()); sleep(wait); } } catch (Exception e) {} } }

    Ce qui donne:
    Le compteur est à : 1 Le compteur est à : 2 Le compteur est à : 3 Le compteur est à : 4 Le compteur est à : 5 Le compteur est à : 6 Interruption des processus

    Ici, ce sont les méthodes de calcul qui sont synchronisées. Tout ce passe comme si un verrou était placé pour monopoliser l’accès au méthode de l'objet. Attention, le verrou est mis lors de l'appel si elle est estampillée "synchronized". Il y a un piège à éviter, les méthodes appelées à l'intérieur d'une méthode synchronisée doivent être également synchronized. Sinon, il y a perte du verrou est toute les actions qui suivront dans la méthode ne seront plus sécurisées!
    void synchronized f1(...){ ... f2(); //Cette fonction n'est pas synchronisée, il y a donc perte du verrou à ce moment précis. ... } void f2(...){ //L'erreur ici est que cette fonction n'est pas synchronisée ... }

    4.2)Synchronisation par bloc

    Il est possible de verrouiller un objet pour un bloc d'instruction, c'est comme si on avait un verrou sur les objets déclaré dans le bloc. Et donc le monopole de l'usage de ces objet:
    synchronized { ... }

    Attention, les tableaux ne supportent pas ce type de code.

    4.3)Le cadencement des Threads entre eux

    Il est possible de cadencer les threads entre eux car on besoin de l'action de l'un pour réaliser l'action de l'autre.Il faut donc rendre l'objet partagé/verrouillé au thread que l'on attend. Pour cela on appelle la fonction wait() de l'objet qui possède le verrou et on notifie les threads par notifyAll() ou notify si on connait le thread en attente.

    Voici un exemple d'un objet réserve partagé entre un processus qui ajoute ajout et un processus qui retire.
    import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class Sync { public static void main (String args[]) { Reserve r = new Reserve () ; ThrAjout ta1 = new ThrAjout (r, 100, 15) ; ThrAjout ta2 = new ThrAjout (r, 50, 20) ; ThrPuise tp = new ThrPuise (r, 300, 10) ; System.out.println ("Suivi de stock --- faire entree pour arreter ") ; ta1.start () ; ta2.start () ; tp.start () ; lireString() ; ta1.interrupt () ; ta2.interrupt () ; tp.interrupt () ; } public static String lireString(){//lecture d'une chaine String ligne_lue=null; try{ InputStreamReader lecteur=new InputStreamReader(System.in); BufferedReader entree=new BufferedReader(lecteur); ligne_lue=entree.readLine(); } catch(IOException err){ System.exit(0); } return ligne_lue; } } class Reserve extends Thread { public synchronized void puise (int v) throws InterruptedException { if (v <= stock) { System.out.print ("-- on puise " + v) ; stock -= v ; System.out.println (" et il reste " + stock ) ; } else { System.out.println ("** stock de " + stock + " insuffisant pour puiser " + v ) ; wait() ; } } public synchronized void ajoute (int v) { stock += v ; System.out.println ("++ on ajoute " + v + " et il y a maintenant " + stock) ; notifyAll() ; } private int stock = 500 ; // stock initial = 500 } class ThrAjout extends Thread { public ThrAjout (Reserve r, int vol, int delai) { this.vol = vol ; this.r = r ; this.delai = delai ; } public void run () { try { while (!interrupted()) { r.ajoute (vol) ; sleep (delai) ; } } catch (InterruptedException e) {} } private int vol ; private Reserve r ; private int delai ; } class ThrPuise extends Thread { public ThrPuise (Reserve r, int vol, int delai) { this.vol = vol ; this.r = r ; this.delai = delai ; } public void run () { try { while (!interrupted()) { r.puise (vol) ; sleep (delai) ; } } catch (InterruptedException e) {} } private int vol ; private Reserve r ; private int delai ; }

    Ce qui affichera:
    -- on puise 300 et il reste 0 ** stock de 0 insuffisant pour puiser 300 ++ on ajoute 50 et il y a maintenant 50 ++ on ajoute 100 et il y a maintenant 150 ** stock de 150 insuffisant pour puiser 300 ++ on ajoute 50 et il y a maintenant 200 ** stock de 200 insuffisant pour puiser 300 ++ on ajoute 100 et il y a maintenant 300 -- on puise 300 et il reste 0 ** stock de 0 insuffisant pour puiser 300 ++ on ajoute 50 et il y a maintenant 50 ++ on ajoute 100 et il y a maintenant 150 ** stock de 150 insuffisant pour puiser 300 ++ on ajoute 100 et il y a maintenant 250 ** stock de 250 insuffisant pour puiser 300 ++ on ajoute 50 et il y a maintenant 300 ++ on ajoute 100 et il y a maintenant 400 -- on puise 300 et il reste 100 ** stock de 100 insuffisant pour puiser 300 ++ on ajoute 50 et il y a maintenant 150 ++ on ajoute 100 et il y a maintenant 250 ** stock de 250 insuffisant pour puiser 300

    Conclusion

    Les threads permettent de créer des process qui agiront simultanément. On les utilise notamment pour ne pas perdre de temps sur une action qui peut être longue comme par exemple un accès au réseau, au système de fichiers ou pour un calcul complexe et coûteux en temps processeur.









    Pseudonyme (obligatoire) :
    Adresse mail (obligatoire) :
    Site web :

    Pour valider votre commentaire, écrivez le texte affiché sur l'image :



    © 2017 www.doritique.fr par Robert DORIGNY