Les unités d'exécution (ou threads) peuvent être du type démon ou utilisateur, respectivement dépendant ou indépendant du thread d'où ils ont été lancés.

Un thread démon dépend du thread parent qui l'a lancé et s'exécute en arrière plan de ce-dernier. Par exemple, lorsque la méthode principale (main) du thread parent se termine, les éventuels threads démons lancés cessent également d'exister et cela même si leurs traitements étaient en cours d'exécution.

Afin de pallier à cette situation, il est possible d'utiliser la méthode isAlive() afin de tester si le thread démon est encore actif, et de prendre les mesures appropriées dans le but de retarder la fin du thread courant.

while (threadDemon.isAlive()){
  //Mise en sommeil du thread courant...
  Thread.sleep(100);
}

Un thread utilisateur peut poursuivre son exécution même si son thread parent est terminé. Par exemple, si la méthode main() du thread parent s'arrête, les éventuels threads utilisateurs continuent malgré cela, leur traitement.

Un thread démon ou utilisateur se définit par l'intermédiaire d'une méthode setDaemon() de la classe Thread.

//Définit un thread en tant que démon
unThread.setDaemon(true);
//Définit un thread utilisateur
Thread.setDaemon(false);

La méthode setDaemon() doit impérativement être appelée avant que l'unité d'exécution ne soit démarrée, dans le cas contraire une exception IllegalThreadStateException serait levée.

Thread unThread = new ClasseThread(arguments);
unThread.setDaemon(true);
unThread.start();

Il est possible également, qu'une exception SecurityException puisse être levée si le thread courant n'est pas autorisé à modifier une unité d'exécution fils.

La méthode isDaemon() retournant une valeur booléenne, permet de tester si un unité d'exécution, est (true) ou n'est pas (false) un thread démon.

//Classe Boucle descendante de la superclasse Thread
public class Boucle extends Thread {
  int fin;
  int n;
  //Le constructeur définit le nombre d'itération à accomplir.
  public Boucle (int fin, int n) {
    this.fin = fin;
    this.n = n;
  }
  //Redéfinition de la méthode run().
  public void run() {
    System.out.println(this.getName() + " démarre...");
    //Boucle
    for (int i = 1; i <= fin ; i++) {
      //Affichage du nom du thread courant et du compteur.
      System.out.println(this.getName() + " ("
                                 + (this.isDaemon() ? "   Démon   " : "Utilisateur") 
                                 + ") " + i);
      try{
        //Pause de n ms.
        Thread.sleep(n);
      }
      catch(InterruptedException e){
        e.printStackTrace();
      }
    }
    System.out.println(this.getName() + " se termine...");
  }
}

//Classe Application
public class Application {
  public static void main (String args[]) {
    //Instanciation de deux threads...
    int n = 50;
    Thread t0 = new Boucle (12, n);
    Thread t1 = new Boucle (90, n);
    Thread t2 = new Boucle (60, n);
    Thread t3 = new Boucle (30, n);
    //Déclaration de ce thread en tant qu'utilisateur.
    t0.setDaemon(false);
    //Déclaration des threads en tant que démons.
    t1.setDaemon(true);
    t2.setDaemon(true);
    t3.setDaemon(true);
    //Démarrage des threads...
    t0.start();
    t1.start();
    t2.start();
    t3.start();
    //Tant que des threads démons sont actifs...
    while (t1.isAlive() || t2.isAlive() || t3.isAlive()) {
      try {
        System.out.println("Au moins un thread est encore actif !");
        //Mise en sommeil du thread courant pendant n ms...
        Thread.sleep(n);
      } 
      catch (InterruptedException e) {
        e.printStackTrace();
        return;
      }
    }
    System.out.println("\nFin de la méthode main() !\n");
  }
}

Dans cet exemple simple, on peut remarquer qu'un thread démon s'arrête lorsque la méthode main() se termine, c'est-pourquoi, il est nécessaire de tester si les threads démons (t1, t2 et t3) sont encore actifs par l'intermédiaire de la méthode isAlive() afin de prolonger avec la méthode sleep() le cycle de vie du thread courant, c'est-à-dire, celui qui exécute la méthode principale et encapsule les autres threads. En outre, le thread utilisateur (t0) continue effectivement son exécution, y compris lorsque la méthode main() s'est effectivement terminée.

Par ailleurs, la synchronisation des threads est assurée en fournissant, lors de l'instanciation de toutes les unités d'exécution, une valeur n en millisecondes, employée dans les méthodes sleep() des deux classes mises en jeu. L'affectation d'une valeur différente entraînera indubitablement un fonctionnement asynchrone «cahotique».

Dans le cas où le thread utilisateur t0 possèderait un nombre d'itérations supérieur aux threads démons, en l'occurrence que t0 ait une espérance de vie plus longue que les threads t1, t2 et t3. Alors ces derniers se termineront correctement en bénéficiant du temps d'exécution du thread utilisateur au sein de la méthode main(). Autrement dit, tant qu'il existe au moins un thread utilisateur actif, des threads démons pourront s'exécuter.

//Classe Application
public class Application {
  public static void main (String args[]) {
    //Instanciation de deux threads...
    int n = 50;
    Thread t0 = new Boucle (120, n);
    Thread t1 = new Boucle (90, n);
    Thread t2 = new Boucle (60, n);
    Thread t3 = new Boucle (30, n);
    //Déclaration de ce thread en tant qu'utilisateur.
    t0.setDaemon(false);
    //Déclaration des threads en tant que démons.
    t1.setDaemon(true);
    t2.setDaemon(true);
    t3.setDaemon(true);
    //Démarrage des threads...
    t0.start();
    t1.start();
    t2.start();
    t3.start();
    System.out.println("\nFin de la méthode main() !\n");
  }
}

Enfin, dans cet exemple cinq threads s'exécutent simultanément, quatre sont créés artificiellement et un autre, englobant ceux précités, est créé automatiquement lors du démarrage du programme. C'est pourquoi, si l'on modifie la valeur n de l'instruction Thread.sleep(n) dans la boucle while, le nombre d'exécution des threads enfants se modifieront proportionnellement par rapport au temps de pause du thread parent.

public class Application extends Thread {
  private static int n = 0; //nombre d'applications lancées
  private static int nb = 0; //nombre de threads exécutés
  Application () {
        n++;
        System.out.println("Le thread " 
                  + (this.isDaemon() ? "démon " : "utilisateur ") 
                  + Thread.currentThread().getName() 
                  + "-" + n + " démarre..." );
    }

    public void run() {
    int loc = nb;
        System.out.println(Thread.currentThread().getName() + " ("
            + (this.isDaemon() ? "   Démon   " : "Utilisateur")
                + ") demarre..." );
         try {
             Thread.sleep(100);
         }
         catch (InterruptedException e) {
           e.printStackTrace();
            return;
         }
    loc++;
    nb = loc;
    System.out.println("loc = " + loc + "; nb = " + nb);
        System.out.println(Thread.currentThread().getName() 
                                                                               + " se termine.");
    }

    public static void main(String args[]) {
        System.out.println("Le thread main() principal demarre..");
        // Création de 3 threads
        for (int i=0; i < 3; i++) {
            Thread app = new Application();
            app.start();
        }
        System.out.println("nb = " + nb + "; n = " + n);
        System.out.println("Le thread main() principal se termine.");
    }
}

Dans cet exemple, si l'on supprime le bloc try... catch permettant d'appliquer une pause au thread courant, le programme fonctionne très différemment puisque la simultanéité des threads n'est plus effective. Chaque thread main s'exécute les uns après les autres et se termine lorsque le thread principal s'arrête. Quant aux threads utilisateurs, on remarquera qu'ils s'exécutent séquentiellement, lorsqu'un s'arrête, le suivant prend le relai.

Les variables statiques n et nb fournissent un résultat différent avant la fin du programme, car la première compte les instanciation de la classe Application et la seconde tente de dénombrer les threads main au sein de la méthode run() sans y parvenir.
Le résultat nul de nb est dû au fonctionnement simultané des threads.

  • Lorsque les threads main-x démarrent, la variable partagée nb égale à zéro puisqu'aucun des threads n'est à ce moment terminé, est affectée à la variable locale loc.
  • Lorsque les threads main-x se terminent, la variable locale loc, étant égale à 1 après une incrémentation, est affectée à nb. Chacun des threads, modifiant la variable globale nb de la même façon, produit en fin de programme un résultat égal à 1.

En supprimant le bloc try... catch de la méthode run(), le contenu des variables n et nb devient identique, dû à un fonctionnement séquentiel des threads.

Téléchargez les exemples