Écrivons un premier thread par extension de la classe Thread (package java.lang). Nous devons surcharger une seule méthode : la méthode run(), qui retourne void, et ne prend aucun paramètre. Cette méthode ne jette non plus aucune exception, et nous verrons que cela pose en fait un problème.


Lancer un thread signifie demander à la machine Java d'exécuter cette classe dans un fil d'exécution particulier. Par convention, le code exécuté dans ce fil est le code écrit dans la méthode run() de ce thread.

Voyons comment lancer ce thread.


Le lancement d'un nouveau thread se fait par appel à la méthode start() de l'instance de Thread que l'on veut exécuter. Notons ici qu'il s'agit bien de la méthode start(), et non pas de la méthode run(), que l'on a surchargée. Appeler la méthode run() au lieu de start() permet d'exécuter le code de ce thread , mais dans le fil d'exécution courant. Aucun nouveau fil n'est créé dans ce cas, ce n'est en général pas ce que l'on veut ! Appeler run() à la place de start() est une erreur que l'on rencontre parfois.

L'appel à cette méthode start() a pour effet de créer un nouveau fil d'exécution dans la machine Java. Ce fil d'exécution va exécuter la méthode run() de cet objet. C'est pourquoi l'on surcharge cette méthode.

Dans le cas de notre exemple, l'appel à la méthode b.start() rend la main immédiatement. On voit donc s'afficher dans la console, immédiatement, le message Thread lancé. Contrairement à l'exécution d'un programme classique, la méthode main() ne quitte pas une fois son exécution terminée. Une fois toutes les 10 millisecondes, on voit le message Je vogue ! s'afficher. Une fois qu'il s'est affiché 100 fois, alors notre application exemple se termine.

Que se passe-t-il lorsque l'on appelle par erreur la méthode run() plutôt que la méthode start() ? La méthode run() va s'exécuter, mais dans le thread courant, puisque seul l'appel à start() déclenche la création d'un nouveau thread au niveau de la machine Java. On pourra avoir l'impression que tout se passe normalement, puisque les traitements seront tout de même effectués. Cela dit, comme tout se passe dans le même fil d'exécution, l'ordre des affichages n'est pas le même. Les messages Je vogue ! s'affichent tout d'abord. Suit enfin le message Thread lancé, prometteur, mais erroné !

D'une façon générale, une application qui lance des threads ne peut pas quitter normalement tant que tous ces threads sont encore actifs. Il faut donc les arrêter, nous verrons comment. Un type de thread particulier, appelé daemon ne présente pas cette contrainte.

La première façon de lancer un thread est la plus simple, mais elle n'est en général pas utilisable, du fait qu'une classe Java ne peut pas étendre deux classes à la fois.

Il existe donc une deuxième façon de faire, qui commence par implémenter l'interface Runnable. Cette interface n'expose qu'une unique méthode : run().


Cette deuxième classe ressemble énormément à la première, c'est en fait la même à un détail près : elle implémente Runnable au lieu d'étendre Thread.

La façon dont on lance un tel thread , est en revanche fondamentalement différente. Une instance de cette classe doit être passée en paramètre du constructeur d'une nouvelle instance de Thread, et c'est sur ce nouveau thread que nous allons invoquer la méthode start().


L'objet de type Thread que l'on crée ici est complètement générique. Appeler sa méthode start() demande toujours à la machine Java de créer un nouveau fil d'exécution, et le fonctionnement interne de la classe Thread fait que ce fil va exécuter la méthode run() de l'objet passé en paramètre lors de la construction de notre instance de Thread.

Arrêter un thread n'est pas une chose si simple. En tout cas, il y a une chose qu'il ne faut pas surtout pas faire pour stopper un thread , c'est appeler sa méthode stop(). Cette méthode est dépréciée, et ne doit plus être utilisée.

On ne peut pas, dans le cadre de l'API Concurrent, arrêter un thread de l'extérieur, de façon autoritaire. La bonne façon d'arrêter un thread , est de positionner un champ particulier de ce thread . Il est alors de la responsabilité du thread de vérifier périodiquement la valeur de ce champ, et lorsqu'elle lui indique qu'il faut qu'il s'interrompe, c'est à lui de le faire.

Cette approche est valide dans la plupart des cas, mais pas dans le cas où le thread qui doit s'arrêter, est lui-même en attente du retour d'une opération bloquante, ou dans un état d'attente (méthodes Object.wait(long millis) ou Thread.sleep(long millis). Dans ce cas, on peut interrompre le thread . Ces méthodes d'attente sont en général capables de détecter cette interruption, et jettent alors une exception de type InterruptedException.


Le thread de l'exemple précédent peut être interrompu dans tous les cas par appel à sa méthode cancel().

Notons le comportement d'un thread interrompu.

  1. positionnement à true de la propriété interrupted ;

  2. si le thread est dans une opération bloquante, qui supporte l'interruption, alors cette opération remet interrupted à false, et jette une exception de type InterruptedException ;

  3. si ce n'est pas le cas, interrupted est laissé à true. Sa valeur peut être testée par appel à Thread.currentThread().isInterrupted(). Il est de la responsabilité du traitement de s'interrompre alors.

Java API avancées
Retour au blog Java le soir
Cours & Tutoriaux

Table des matières

API Collection
1. Introduction
2. Interface Collection
2.1. Notion de Collection
2.2. Détail des méthodes disponibles
2.3. Interface Iterator
2.4. Implémentation, exemples d'utilisation
3. Interface List
3.1. Notion de List
3.2. Détail des méthodes disponibles
3.3. Interface ListIterator
3.4. Implémentations, exemples d'utilisation
4. Interface Set
4.1. Notion de Set
4.2. Implémentations HashSet et LinkedHashSet
4.3. Exemples d'utilisation
5. Interface SortedSet
5.1. Notion de SortedSet
5.2. Détails des méthodes disponibles
5.3. Exemples d'utilisation
6. Interface NavigableSet
6.1. Notion de NavigableSet
6.2. Détails des méthodes disponibles
6.3. Exemple d'utilisation
7. Interfaces Queue et Deque
7.1. Notion de file d'attente
7.2. Détail des méthodes disponibles
7.3. Utilisation des interfaces Queue et Deque
8. Tables de hachage
8.1. Notion de table de hachage
8.2. Interface Map
8.3. Interface Map.Entry
8.4. Interface SortedMap
8.5. Interface NavigableMap
8.6. Implémentations
8.7. Exemples d'utilisation
9. Classes utilitaires Collections et Arrays
9.1. Introduction
9.2. Classe Arrays
9.3. Classe Collections
Génériques
1. Introduction
2. Un premier exemple
2.1. Une première classe générique
2.2. Une première méthode générique
3. Contraindre un type générique
3.1. Problème posé
3.2. Contraindre un type générique
4. Implémentation des génériques
4.1. Type erasure
4.2. Types génériques et casts
4.3. Type générique et exception
4.4. Construction d'une instance générique
4.5. Génériques et membres statiques
4.6. Collisions de méthodes génériques
4.7. Implémentation de plusieurs types identiques
5. Type <?>
5.1. Introduction
5.2. Type ? extension d'un type
5.3. Type ? super-type d'un type
Expressions régulières
1. Introduction
2. Mise en œuvre des expressions régulières
2.1. Fonctionnement d'une regexp
2.2. Fonctionnement de l'API en Java
2.3. Un premier exemple
2.4. Classe Pattern
2.5. Classe Matcher
2.6. Utilisation des méthode find() et group()
2.7. Méthodes de remplacement
2.8. Sélection de régions
3. Syntaxe des expressions régulières
3.1. Notion de classe
3.2. Étude d'un cas réel
3.3. Recherche d'un mot précis
3.4. Recherche de deux mots précis
3.5. Recherche d'un mot commençant par une lettre donnée
3.6. Cas de mots comportant des caractères accentués
3.7. Recherche sur les lignes
Introspection
1. Introduction
2. La classe Class
2.1. Utilisation de Class
2.2. Méthodes disponibles
2.3. Remarque sur la propriété Accessible
2.4. Type d'une classe
2.5. Création d'une instance à partir d'un objet Class
2.6. Cas des énumérations
3. Les classes Method et Constructor
3.1. Utilisation de Method
3.2. Utilisation de Constructor
3.3. Méthodes disponibles
3.4. Invocation d'une méthode par introspection
4. La classe Field
4.1. Utilisation de Field
4.2. Méthodes disponibles
4.3. Accès à un champ par introspection
5. La classe Modifier
Programmation concurrente
1. Introduction
2. Lançons nos premiers threads
2.1. Introduction
2.2. Un premier thread, extension de Thread
2.3. Un deuxième thread, implémentation de Runnable
2.4. Remarque sur la méthode Thread.sleep(long)
2.5. Arrêter un thread
3. Concurrence d'accès
3.1. Notion d'état
3.2. Exemple de concurrence d'accès sur un état
3.3. Analyse de la concurrence d'accès
3.4. Solution au problème
3.5. Champs volatile
4. Synchronisation
4.1. Définition d'un bloc synchronisé
4.2. Fonctionnement d'un bloc synchronisé
4.3. Notion de deadlock
4.4. Bonnes pratiques pour la synchronisation de threads
5. Opérations atomiques
5.1. Atomicité d'une opération
5.2. Solutions disponibles
5.3. Variables atomiques
6. Collections synchronisées et concurrentes
6.1. Introduction
6.2. Position du problème
6.3. Solutions proposées
7. Files d'attente
7.1. Introduction, pattern producteur / consommateur
7.2. Interface BlockingQueue<E>
7.3. Implémentations de BlockingQueue
7.4. Exemple de producteur / consommateur
7.5. Arrêter un producteur / consommateur : pilule empoisonnée
8. Classes utilitaires de l'API Concurrent
8.1. Introduction
8.2. Énumération TimeUnit
8.3. Interface Callable<V>
8.4. Interfaces Future<V> et RunnableFuture<V>
8.5. Interface ScheduledFuture<V> et RunnableScheduledFuture<V>
9. Pattern executor
9.1. Notion de réserve de threads
9.2. Interface Executor
9.3. Interface ExecutorService
9.4. Interface ScheduledExecutorService
9.5. Classe Executors
9.6. Pattern de lancement de tâches
10. Classes de contrôle d'accès
10.1. Introduction
10.2. Interfaces Lock et ReadWriteLock
10.3. Notion de verrou réentrant
10.4. Classe RentrantLock
10.5. Classe ReadWriteRentrantLock
11. Sémaphores, barrières et latches
11.1. Introduction
11.2. Notion de sémaphore, classe Semaphore
11.3. Notion de latch, classe CountDownLatch
11.4. Notion de barrière, classe CyclicBarrier