Aller au contenu

Mon premier thread

Créer un nouveau thread en Java est assez simple. Il suffit d'instancier un nouveau thread, de fournir le code à exécuter et de démarrer le thread :

Thread monPremierThread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Voici mon premier thread");
    }
});
monPremierThread.start();

Note

Le code ci-dessus peut être simplifié en utilisant une fonction lambda. Les deux blocs de code donnent un résultat identique.

Thread monPremierThread = new Thread(() -> {
    System.out.println("Voici mon premier thread");
});
monPremierThread.start();

Lors de l'exécution, un second thread sera lancé et affichera un message.

Exécuter plusieurs threads

Plusieurs threads peuvent être exécutés selon le même principe. Le code s'exécutera alors "en même temps" :

// Premier thread
Thread thread1 = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        System.out.println("Thread 1 : " + i);
    }
});

// Second thread
Thread thread2 = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        System.out.println("Thread 2 : " + i);
    }
});

// Démarrer les threads
thread1.start();
thread2.start();

// Boucler dans le thread principal
for (int i = 0; i < 10; i++) {
    System.out.println("Main : " + i);
}

Chaque exécution donnera un résultat différent à la console :

Thread 2 : 0
Thread 2 : 1
Main : 0
Main : 1
Main : 2
Main : 3
Main : 4
Main : 5
Main : 6
Main : 7
Thread 1 : 0
Thread 2 : 2
...
Main : 0
Main : 1
Thread 2 : 0
Thread 2 : 1
Thread 1 : 0
Main : 2
Main : 3
Main : 4
Main : 5
Main : 6
Main : 7
Thread 2 : 2
...
Thread 2 : 0
Thread 2 : 1
Main : 0
Main : 1
Main : 2
Thread 1 : 0
Thread 2 : 2
Main : 3
Thread 1 : 1
Thread 2 : 3
Main : 4
Thread 1 : 2
...

Attendre la fin des threads avant de poursuivre

Une fois un thread lancé, son exécution se fera dans un processus séparé et le code du thread principal pourra continuer à s'exécuter. Il pourrait parfois utile d'attendre la fin d'un thread avant de poursuivre le programme. La méthode join() sert justement à ça.

Lorsque la méthode join() est appelée sur un thread, le programme attendra que celui-ci se termine avant de continuer. Par exemple, si on veut télécharger un fichier, attendre la fin du téléchargement avant de quitter l'application peut être utile. Même chose pour déconnecter sa clé USB de l'ordinateur, on doit attendre que les fichiers aient terminés de s'écrire.

// Premier thread
Thread thread1 = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        System.out.println("Thread 1 : " + i);
    }
});

// Second thread
Thread thread2 = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        System.out.println("Thread 2 : " + i);
    }
});

// Démarrer les threads
thread1.start();
thread2.start();

// Attendre la fin des threads
try {
    thread1.join();
    thread2.join();
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}

// Boucler dans le thread principal
for (int i = 0; i < 10; i++) {
    System.out.println("Main : " + i);
}

Chaque exécution donnera encore une fois un résultat différent, à l'exception que le thread principal devra attendre que les deux threads aient complètement terminés avant de poursuivre. Le code sera bloqué aux instructions join().

...
Thread 2 : 7
Thread 2 : 8
Thread 1 : 8
Thread 2 : 9
Thread 1 : 9
Main : 0
Main : 1
Main : 2
Main : 3
Main : 4
Main : 5
Main : 6
Main : 7
Main : 8
Main : 9
...
Thread 2 : 5
Thread 2 : 6
Thread 2 : 7
Thread 2 : 8
Thread 2 : 9
Main : 0
Main : 1
Main : 2
Main : 3
Main : 4
Main : 5
Main : 6
Main : 7
Main : 8
Main : 9
...
Thread 1 : 5
Thread 1 : 6
Thread 1 : 7
Thread 1 : 8
Thread 1 : 9
Main : 0
Main : 1
Main : 2
Main : 3
Main : 4
Main : 5
Main : 6
Main : 7
Main : 8
Main : 9

Interface Runnable

Runnable est une interface ayant une seule méthode à remplacer : run(). On peut alors permettre à une classe existante de s'exécuter en tant que thread en lui faisant implémenter l'interface Runnable. Imaginons une classe permettant d'enregistrer chaque nombre dans un fichier. Tout dépendant de la quantité, celà pourrait prendre plusieurs secondes :

1
2
3
4
5
public static void main(String[] args) {
    GenererNombres genererNombres = new GenererNombres(Integer.MAX_VALUE);
    genererNombres.enregistrerFichier("nombres_1.txt");
    genererNombres.enregistrerFichier("nombres_2.txt");
}
import java.io.File;
import java.io.PrintWriter;

public class GenererNombres {

    private static final int MAX_VALUE = 0xfffffff; // 268435455
    private int maxNombre = 0;
    public GenererNombres(int maxNombre) {
        this.maxNombre = Math.min(maxNombre, MAX_VALUE);
    }

    public void enregistrerFichier(String nomFichier) {
        System.out.println("Enregistrement du fichier " + nomFichier);
        File fichier = new File(nomFichier);
        try (PrintWriter printWriter = new PrintWriter(fichier)) {
            for (int i = 0; i < this.maxNombre; i++) {
                printWriter.println(i);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.println("Fichier " + nomFichier + " enregistré");
    }
}
Enregistrement du fichier nombres_1.txt
Fichier nombres_1.txt enregistré
Enregistrement du fichier nombres_2.txt
Fichier nombres_2.txt enregistré

Temps total d'exécution : 47512 ms

En lançant l'exécution, le premier fichier doit être terminé avant de débuter le second.

En modifiant légèrement la classe pour lui permettre d'implémenter Runnable, celle-ci pourrait effectuer son travail dans un thread, permettant d'accélérer l'exécution.

1
2
3
4
5
6
7
8
public static void main(String[] args) {
    GenererNombres genererNombres1 = new GenererNombres(Integer.MAX_VALUE, "nombres_1.txt");
    GenererNombres genererNombres2 = new GenererNombres(Integer.MAX_VALUE, "nombres_2.txt");
    Thread thread1 = new Thread(genererNombres1);
    Thread thread2 = new Thread(genererNombres2);
    thread1.start();
    thread2.start();
}
import java.io.File;
import java.io.PrintWriter;

public class GenererNombres implements Runnable {

    private static final int MAX_VALUE = 0xfffffff; // 268435455
    private int maxNombre = 0;
    private String nomFichier;

    public GenererNombres(int maxNombre, String nomFichier) {
        this.maxNombre = Math.min(maxNombre, MAX_VALUE);
        this.nomFichier = nomFichier;
    }

    public void enregistrerFichier() {
        System.out.println("Enregistrement du fichier " + this.nomFichier);
        File fichier = new File(this.nomFichier);
        try (PrintWriter printWriter = new PrintWriter(fichier)) {
            for (int i = 0; i < this.maxNombre; i++) {
                printWriter.println(i);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.println("Fichier " + this.nomFichier + " enregistré");
    }

    @Override
    public void run() {
        this.enregistrerFichier();
    }
}
Enregistrement du fichier nombres_1.txt
Enregistrement du fichier nombres_2.txt
Fichier nombres_1.txt enregistré
Fichier nombres_2.txt enregistré

Temps total d'exécution : 30585 ms, une amélioration de 35%.