Introduction

Le multithreading a été intégré à Java dès le début. Alors, regardons brièvement cette chose appelée multithreading. Mieux ensemble : Java et la classe Thread.  Partie I — Threads d'exécution - 1Nous prenons la leçon officielle d'Oracle comme point de référence : " Leçon : L'application "Hello World! ". Nous allons légèrement modifier le code de notre programme Hello World comme suit :

class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]);
    }
}
argsest un tableau de paramètres d'entrée passés au démarrage du programme. Enregistrez ce code dans un fichier dont le nom correspond au nom de la classe et porte l'extension .java. Compilez-le à l'aide de l' utilitaire javacjavac HelloWorldApp.java : . Ensuite, nous exécutons notre code avec un paramètre, par exemple, "Roger": java HelloWorldApp Roger Mieux ensemble : Java et la classe Thread.  Partie I — Threads d'exécution - 2Notre code a actuellement une grave faille. Si vous ne transmettez aucun argument (c'est-à-dire exécutez simplement "java HelloWorldApp"), nous obtenons une erreur :

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
Une exception (c'est-à-dire une erreur) s'est produite dans le thread nommé "main". Alors, Java a des threads ? C'est là que notre voyage commence.

Java et threads

Pour comprendre ce qu'est un thread, vous devez comprendre comment un programme Java démarre. Modifions notre code comme suit :

class HelloWorldApp {
    public static void main(String[] args) {
		while (true) { 
			// Do nothing
		}
	}
}
Maintenant, compilons-le à nouveau avec javac. Pour plus de commodité, nous exécuterons notre code Java dans une fenêtre séparée. Sous Windows, cela peut être fait comme ceci : start java HelloWorldApp. Nous allons maintenant utiliser l' utilitaire jps pour voir quelles informations Java peut nous donner : Mieux ensemble : Java et la classe Thread.  Partie I — Threads d'exécution - 3Le premier nombre est le PID ou Process ID. Qu'est-ce qu'un processus ?

A process is a combination of code and data sharing a common virtual address space.
Avec les processus, différents programmes sont isolés les uns des autres lors de leur exécution : chaque application utilise sa propre zone en mémoire sans interférer avec les autres programmes. Pour en savoir plus, je vous conseille de lire ce tutoriel : Processes and Threads . Un processus ne peut pas exister sans thread, donc si un processus existe, alors il a au moins un thread. Mais comment cela se passe-t-il en Java ? Lorsque nous démarrons un programme Java, l'exécution commence par la mainméthode. C'est comme si nous entrions dans le programme, donc cette mainméthode spéciale s'appelle le point d'entrée. La mainméthode doit toujours être "public static void", afin que la machine virtuelle Java (JVM) puisse commencer à exécuter notre programme. Pour plus d'informations, Pourquoi la méthode principale de Java est-elle statique ?. Il s'avère que le lanceur Java (java.exe ou javaw.exe) est une simple application C : il charge les différentes DLL qui composent en réalité la JVM. Le lanceur Java effectue un ensemble spécifique d'appels JNI (Java Native Interface). JNI est un mécanisme permettant de connecter le monde de la machine virtuelle Java au monde de C++. Ainsi, le lanceur n'est pas la JVM elle-même, mais plutôt un mécanisme pour la charger. Il connaît les bonnes commandes à exécuter pour démarrer la JVM. Il sait comment utiliser les appels JNI pour configurer l'environnement nécessaire. La configuration de cet environnement comprend la création du thread principal, qui est appelé "main", bien sûr. Pour mieux illustrer quels threads existent dans un processus Java, nous utilisons le jvisualvmoutil, qui est inclus avec le JDK. Connaissant le pid d'un processus, nous pouvons immédiatement voir des informations sur ce processus : jvisualvm --openpid <process id> Mieux ensemble : Java et la classe Thread.  Partie I — Threads d'exécution - 4Il est intéressant de noter que chaque thread a sa propre zone distincte dans la mémoire allouée au processus. Cette structure de mémoire s'appelle une pile. Une pile est constituée de cadres. Un cadre représente l'activation d'une méthode (un appel de méthode inachevé). Un cadre peut également être représenté sous la forme d'un StackTraceElement (voir l'API Java pour StackTraceElement ). Vous pouvez trouver plus d'informations sur la mémoire allouée à chaque thread dans la discussion ici : " Comment Java (JVM) alloue la pile pour chaque thread ". Si vous regardez l' API Java et recherchez le mot "Thread", vous trouverez le java.lang.Threadclasse. C'est la classe qui représente un thread en Java, et nous devrons travailler avec elle. Mieux ensemble : Java et la classe Thread.  Partie I — Threads d'exécution - 5

java.lang.Thread

En Java, un thread est représenté par une instance de la java.lang.Threadclasse. Vous devez immédiatement comprendre que les instances de la classe Thread ne sont pas elles-mêmes des threads d'exécution. Il s'agit simplement d'une sorte d'API pour les threads de bas niveau gérés par la JVM et le système d'exploitation. Lorsque nous démarrons la JVM à l'aide du lanceur Java, il crée un mainthread appelé "main" et quelques autres threads de maintenance. Comme indiqué dans le JavaDoc pour la classe Thread : When a Java Virtual Machine starts up, there is usually a single non-daemon thread. Il existe 2 types de threads : les démons et les non-démons. Les threads démons sont des threads d'arrière-plan (de gestion interne) qui effectuent un certain travail en arrière-plan. Le mot "démon" fait référence au démon de Maxwell. Vous pouvez en savoir plus dans cet article de Wikipedia . Comme indiqué dans la documentation, la JVM continue d'exécuter le programme (processus) jusqu'à :
  • La méthode Runtime.exit() est appelée
  • Tous les threads NON-démons terminent leur travail (sans erreurs ou avec des exceptions levées)
Un détail important en découle : les threads démons peuvent être arrêtés à tout moment. En conséquence, il n'y a aucune garantie quant à l'intégrité de leurs données. En conséquence, les threads démons conviennent à certaines tâches de maintenance. Par exemple, Java a un thread responsable du traitement finalize()des appels de méthode, c'est-à-dire des threads impliqués dans le Garbage Collector (gc). Chaque thread fait partie d'un groupe ( ThreadGroup ). Et les groupes peuvent faire partie d'autres groupes, formant une certaine hiérarchie ou structure.

public static void main(String[] args) {
	Thread currentThread = Thread.currentThread();
	ThreadGroup threadGroup = currentThread.getThreadGroup();
	System.out.println("Thread: " + currentThread.getName());
	System.out.println("Thread Group: " + threadGroup.getName());
	System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
Les groupes mettent de l'ordre dans la gestion des threads. En plus des groupes, les threads ont leur propre gestionnaire d'exceptions. Jetez un oeil à un exemple:

public static void main(String[] args) {
	Thread th = Thread.currentThread();
	th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
		@Override
		public void uncaughtException(Thread t, Throwable e) {
			System.out.println("An error occurred: " + e.getMessage());
		}
	});
    System.out.println(2/0);
}
La division par zéro provoquera une erreur qui sera interceptée par le gestionnaire. Si vous ne spécifiez pas votre propre gestionnaire, la JVM appellera le gestionnaire par défaut, qui affichera la trace de la pile de l'exception sur StdError. Chaque thread a également une priorité. Vous pouvez en savoir plus sur les priorités dans cet article : Java Thread Priority in Multithreading .

Création d'un fil

Comme indiqué dans la documentation, nous avons 2 façons de créer un thread. La première consiste à créer votre propre sous-classe. Par exemple:

public class HelloWorld{
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello, World!");  
        }
    }
    
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
    }
}
Comme vous pouvez le voir, le travail de la tâche se déroule dans la run()méthode, mais le thread lui-même est démarré dans la start()méthode. Ne confondez pas ces méthodes : si nous appelons un()directement la méthode r, alors aucun nouveau thread ne sera lancé. C'est la start()méthode qui demande à la JVM de créer un nouveau thread. Cette option où nous héritons de Thread est déjà mauvaise dans la mesure où nous incluons Thread dans notre hiérarchie de classes. Le deuxième inconvénient est que nous commençons à violer le principe de "responsabilité unique". C'est-à-dire que notre classe est simultanément responsable du contrôle du thread et de certaines tâches à effectuer dans ce thread. Quelle est la bonne manière ? La réponse se trouve dans la même run()méthode, que nous redéfinissons :

public void run() {
	if (target != null) {
		target.run();
	}
}
Voici targetsome java.lang.Runnable, que nous pouvons passer lors de la création d'une instance de la classe Thread. Cela signifie que nous pouvons faire ceci :

public class HelloWorld{
    public static void main(String[] args) {
        Runnable task = new Runnable() {
            public void run() {
                System.out.println("Hello, World!");
            } 
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Runnableest également une interface fonctionnelle depuis Java 1.8. Cela permet d'écrire du code encore plus beau pour la tâche d'un thread :

public static void main(String[] args) {
	Runnable task = () -> { 
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

Conclusion

J'espère que cette discussion clarifie ce qu'est un thread, comment les threads existent et quelles opérations de base peuvent être effectuées avec les threads. Dans la partie suivante , nous essaierons de comprendre comment les threads interagissent les uns avec les autres et d'explorer le cycle de vie des threads. Mieux ensemble : Java et la classe Thread. Partie II — Synchronisation Mieux ensemble : Java et la classe Thread. Partie III — Interaction Mieux ensemble : Java et la classe Thread. Partie IV — Callable, Future et friends Mieux ensemble : Java et la classe Thread. Partie V — Executor, ThreadPool, Fork/Join Mieux ensemble : Java et la classe Thread. Partie VI — Tirez !